learnleetcode

2352. Equal Row and Column Pairs

Medium · solved 2026-10-01 · Open on LeetCode

hash-tablematrixarray

Time O(n²) · Space O(n²)

How to get there

Group by sequence, then multiply

1. Solve it by hand. On LeetCode’s example 2, compare each row with each column, number by number: row 0 equals column 0, and rows 2 and 3 both equal column 2. That is 3 pairs, found by n × n comparisons of n numbers each: O(n³).

2. Ask what a pair really needs. A row and a column pair up exactly when they read the same sequence. So instead of comparing them two at a time, give each sequence a name and compare names. Joining the numbers with a separator, as in "2_4_2_2", gives that name: two sequences are equal exactly when their tokens are, because _ never appears inside a number. A token is a string, so it can be a dict key.

3. Count by token, rows and columns apart. A pair is always one row with one column; two equal rows, or two equal columns, are not a pair. So rows and columns get a dict each: rows[token] says how many rows read it, cols[token] how many columns.

4. Turn the counts into pairs. A token held by a rows and b columns makes a × b pairs, since each of those rows pairs with each of those columns. Every pair has exactly one token, so adding a × b over all tokens counts each pair once. A token missing from either side adds 0.

The reusable recipe: to count matching pairs between two groups, give each item a key, count each group by key, and add the product of the two counts for every key.

Let the variables' meanings write the loop

Variable Always means
token one row or column, its numbers joined with _
rows each token mapped to how many rows read so far have it
cols each token mapped to how many columns read so far have it
Code What happened What it restores
token = "_".join(... self.row(grid, i)) row i is read its sequence becomes one key
if token not in rows: rows[token] = 1, else: rows[token] += 1 a row with this token rows counts it, first sighting or repeat
the same lines with self.col and cols column i is read cols counts it
sum(rows[token] * cols[token] for token in rows if token in cols) both dicts are complete each token adds its pairs; a token with no column is skipped, a token with no row is never visited

Rows and columns are counted in the same loop because the grid is square: index i reaches row i and column i together.

The method carries over: keep one count per group, so the two numbers a pair needs stay apart until the multiplication.

Alternatives compared

Approach Time Space How it works
Brute force O(n³) O(1) Compare every row with every column, number by number.
Two dicts of tokens (chosen) O(n²) O(n²) Count rows and columns by token, then add rows × cols for each token.
One dict, looked up by column O(n²) O(n²) Count the rows only; each column then adds rows[token], the number of rows equal to it.
Tuple keys O(n²) O(n²) tuple(row) is hashable, so it can be the key directly, without building a string.

Reading the grid alone takes n² steps, so O(n²) is the best possible. The one-dict version adds the same rows × cols total one column at a time: a token’s rows count gets added once per matching column.

solution.py

class Solution:
    def equalPairs(self, grid: list[list[int]]) -> int:
        n = len(grid) # n x n

        rows, cols = {}, {}

        for i in range(n):
            token = "_".join(str(num) for num in self.row(grid, i))
            if token not in rows:
                rows[token] = 1
            else:
                rows[token] += 1

            token = "_".join(str(num) for num in self.col(grid, i))
            if token not in cols:
                cols[token] = 1
            else:
                cols[token] += 1

        return sum(rows[token] * cols[token] for token in rows if token in cols)

    def row(_, grid: list[list[int]], i: int) -> list[int]:
        return grid[i]

    def col(_, grid: list[list[int]], i: int) -> list[int]:
        return [col[i] for col in grid]

Notes

Write your approach, insights, and mistakes here. Any other file in this folder (e.g. solution.py, attempt-1.py) is rendered below as code.