LeetCode 2352中等哈希/矩阵
相等行列对 图解题解
这道题到底在问什么
给定 n×n 整数矩阵 grid,统计满足「第 i 行 == 第 j 列」的行列对 (i, j) 个数。行与列按相同下标顺序逐个比较。
- 输入
- [[3,2,1],[1,7,6],[2,7,7]]
- 输出
- 1(第 2 行 [2,7,7] = 第 1 列 [2,7,7])
- 输入
- [[1,1],[1,1]]
- 输出
- 4(每行都等于每列)
最优解:一步一步想明白
- 3记住「行存签名 → 逐列查表累加」。下面分两段:先哈希所有行,再扫所有列。
- 4读第 0 行第 0 个数 3,把它接到这一行的签名后面,现在是 (3)。
- 5读第 0 行第 1 个数 2,把它接到这一行的签名后面,现在是 (3,2)。
- 6读第 0 行第 2 个数 1,把它接到这一行的签名后面,现在是 (3,2,1)。
- 7第 0 行完整签名 (3,2,1) 存进哈希表、计 1。哈希只记「签名 → 出现几次」,不在乎它是第几行。
- 8读第 1 行第 0 个数 1,把它接到这一行的签名后面,现在是 (1)。
- 9读第 1 行第 1 个数 7,把它接到这一行的签名后面,现在是 (1,7)。
- 10读第 1 行第 2 个数 6,把它接到这一行的签名后面,现在是 (1,7,6)。
- 11第 1 行完整签名 (1,7,6) 存进哈希表、计 1。哈希只记「签名 → 出现几次」,不在乎它是第几行。
- 12读第 2 行第 0 个数 2,把它接到这一行的签名后面,现在是 (2)。
- 13读第 2 行第 1 个数 7,把它接到这一行的签名后面,现在是 (2,7)。
- 14读第 2 行第 2 个数 7,把它接到这一行的签名后面,现在是 (2,7,7)。
- 15第 2 行完整签名 (2,7,7) 存进哈希表、计 1。哈希只记「签名 → 出现几次」,不在乎它是第几行。
- 16三行的签名都进了哈希表。接下来换个方向,竖着一列一列取出签名,到表里查有没有哪一行和它一模一样。
- 17竖着取第 0 列第 0 个数 3,把列签名接起来,现在是 (3)。
- 18竖着取第 0 列第 1 个数 1,把列签名接起来,现在是 (3,1)。
- 19竖着取第 0 列第 2 个数 2,把列签名接起来,现在是 (3,1,2)。
- 20第 0 列签名 (3,1,2) 在哈希表里没找到,没有哪一行和它完全相同,答案不变,仍是 0 对。
- 21竖着取第 1 列第 0 个数 2,把列签名接起来,现在是 (2)。
- 22竖着取第 1 列第 1 个数 7,把列签名接起来,现在是 (2,7)。
- 23竖着取第 1 列第 2 个数 7,把列签名接起来,现在是 (2,7,7)。
- 24第 1 列签名 (2,7,7) 在哈希表里命中 1 次(第 2 行和它一样)。答案加 1,现在共 1 对。绿色就是这对相等的行和列。
- 25竖着取第 2 列第 0 个数 1,把列签名接起来,现在是 (1)。
- 26竖着取第 2 列第 1 个数 6,把列签名接起来,现在是 (1,6)。
- 27竖着取第 2 列第 2 个数 7,把列签名接起来,现在是 (1,6,7)。
- 28第 2 列签名 (1,6,7) 在哈希表里没找到,没有哪一行和它完全相同,答案不变,仍是 1 对。
- 29全部列扫完,一共 1 对相等的行列(第 2 行 = 第 1 列)。靠「行哈希 + 逐列查表」,只花 O(n²) 就避开了三重循环两两比对。
⚠️ 容易写错的地方
✗ 错:行、列不按同一下标顺序比较
✓ 对:签名保序:第 k 个对第 k 个
行列相等的定义是逐位置相等,顺序错了签名就对不上
✗ 错:碰到一对就 +1
✓ 对:查表命中几次就加几次
相同的行(或列)可能有多个,重复的对都要计入,用计数才不漏
✗ 错:三重循环两两比对
✓ 对:行做哈希、逐列查表
两两比对是 O(n³),哈希签名把找相等行降到一次 O(n) 查表
完整代码(Python / C++ / Java)
Python
from typing import List
from collections import Counter
class Solution:
def equalPairs(self, grid: List[List[int]]) -> int:
rows = Counter(tuple(row) for row in grid)
n = len(grid)
ans = 0
for c in range(n):
ans += rows[tuple(grid[r][c] for r in range(n))]
return ansC++
#include <unordered_map>
#include <vector>
#include <string>
using namespace std;
class Solution {
public:
int equalPairs(vector<vector<int>>& grid) {
unordered_map<string, int> rows;
auto key = [](const vector<int>& v) {
string s;
for (int x : v) { s += to_string(x); s += ','; }
return s;
};
for (auto &row : grid) rows[key(row)]++;
int n = grid.size(), ans = 0;
for (int c = 0; c < n; ++c) {
vector<int> col;
col.reserve(n);
for (int r = 0; r < n; ++r) col.push_back(grid[r][c]);
ans += rows[key(col)];
}
return ans;
}
};Java
import java.util.*;
class Solution {
public int equalPairs(int[][] grid) {
Map<String, Integer> rows = new HashMap<>();
for (int[] row : grid) {
String key = Arrays.toString(row);
rows.put(key, rows.getOrDefault(key, 0) + 1);
}
int n = grid.length, ans = 0;
for (int c = 0; c < n; c++) {
int[] col = new int[n];
for (int r = 0; r < n; r++) col[r] = grid[r][c];
ans += rows.getOrDefault(Arrays.toString(col), 0);
}
return ans;
}
}复杂度
时间
O(n²)
哈希所有行 O(n²)、逐列查表 O(n²),合起来 O(n²)
空间
O(n²)
哈希表里最多存 n 个长度为 n 的行签名
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 相等行列对 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
行签名用什么表示更稳妥?+
可以用不可变元组(Python tuple)、或把整行用分隔符拼成字符串(如 "3#2#1")。要点是带分隔符、保序,避免 [1,23] 和 [12,3] 拼成同一个串这类碰撞。
这道题和「字母异位词分组」有什么共性?+
都是「给每个对象算一个规范化签名,再用哈希按签名聚合 / 匹配」。那题签名是排序后的字符串,这题签名是保序的行 / 列元组,套路一致。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄声线把 相等行列对 一步步讲透(全站 0 节语音精讲,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。