搜索二维矩阵 图解题解
二维矩阵整体有序,和一条升序数组其实是一回事——脑补成数组做二分,行列换算一除一取余,不费吹灰之力。
把矩阵「脑补」成一条拉直的升序数组:每行接着上一行排,整体严格递增。用虚拟下标 0 到 m×n−1 做二分,中点下标除以列数得行号、取余得列号,O(1) 换算回矩阵取真实值——根本不用真的把矩阵拼成数组。每次比一次就砍掉一半范围,log(m×n) 次比较找到答案。
这道题到底在问什么
- 输入
- matrix(6×7 升序), target=104
- 输出
- true
- 输入
- matrix(6×7 升序), target=87
- 输出
- false
先想最直接的笨办法
核心一句话:二维当一维二分。idx÷列数=行,idx%列数=列——这把 O(m·n) 暴力降到 O(log(m·n))。(动画第 3 步)
最优解:为什么这么做
一句话答案:LeetCode 74 搜索二维矩阵:整张表首尾接起来是一条升序序列,当成长度 m·n 的有序数组二分,下标 mid 用 ÷n 取行、%n 取列还原,时间 O(log(m·n))、空间 O(1)。
这道题给的矩阵,行和行之间有什么讲究
给一个 m 行 n 列的矩阵,每行升序,且下一行第一个数比上一行最后一个数大。判断 target 在不在里面,要做到 O(log(m·n))。约定用(行号,列号)定位格子、都从 0 数起。题面 6 行 7 列,target=104 得 true,target=87 落在 86 和 89 之间、表里没有,得 false。
逐个格子比一遍、或者逐行二分,为什么都不够快
42 个格子逐个比最坏 42 次,即 O(m·n),顶不住 O(log(m·n)) 的线。退一步对每行各二分,O(m·log n),外层仍逐行走 m 趟。题目要把 m 也塞进 log,这两种都差一口气。
为什么整张矩阵能当成一条排好序的长队
关键在那条行间约束:每行内部升序,下一行开头又比上一行结尾大,把每行首尾接起来,整张矩阵就是一条完全升序、中间不回头变小的序列。既然有序,就不必管它折成几行——拉直成一维(下标 0 到 m·n-1)当成有序数组,直接二分即可。
下标砍到中间那格,怎么换回它在第几行第几列
起手 lo=0、hi=m·n-1 两个一维下标,罩住全部 42 个数。每轮取中点 mid(正中间那个下标,mid=(lo+hi)//2),把这下标还原回二维:行 = mid÷n(整除),列 = mid%n(取余),值是 matrix[mid÷n][mid%n]。拿它和 target 比,三种走向:等于就找到;比 target 大就丢右半,hi 收到 mid-1;比它小就丢左半,lo 进到 mid+1。
两个 target 各走一轮 lo、mid、hi
题面这张表 6 行 7 列(下标从 0 起),一维下标换回二维按列数 7 分组:÷7 得行、%7 得列。先找 104:lo=0、hi=41,mid=20 是 matrix[2][6]=62<104,lo 到 21;mid=31 是 matrix[4][3]=95<104,lo 到 32;mid=36 是 matrix[5][1]=110>104,hi 收到 35;mid=33 是 matrix[4][5]=101<104,lo 到 34;mid=34 是 matrix[4][6]=104,命中,返回 true。
再找 87:mid=20 的 62<87,lo 到 21;mid=31 的 95>87,hi 到 30;mid=25 是 matrix[3][4]=77<87,lo 到 26;mid=28 是 matrix[4][0]=86<87,lo 到 29;mid=29 是 matrix[4][1]=89>87,hi 到 28。此时 lo=29 超过 hi=28,范围空了,返回 false。
换算行列时除数用错,满盘跟着挪位
还原下标按列数 n 分组:行用 mid÷n、列用 mid%n。把 n 错写成行数 m,行列全乱,取到的不是那格。时间 O(log(m·n)),每轮范围减半;空间 O(1),只用几个下标。循环条件要带等号写 lo<=hi,否则 lo、hi 撞到同一格便不再检查、那格恰是 target 就漏掉;范围大时用 lo+(hi-lo)÷2 防 mid 溢出。
▶ 动画逐步走查(共 24 步)——想跟着动画一帧帧对照就展开
- 3核心一句话:二维当一维二分。idx÷列数=行,idx%列数=列——这把 O(m·n) 暴力降到 O(log(m·n))。
- 4先演示「找得到」:target=104。 把矩阵看成一维数组,下标范围 lo=0 .. hi=41(共 42 个数全部存活),开始二分。
- 5取中点 mid=20:行=20÷7=2,列=20%7=6 → matrix[2][6]=62。和 target=104 比一下。
- 662 < 104:mid 这个数太小,它和它左边(更小的)21 个数全部排除 → 丢掉,lo 进到 21。
- 7取中点 mid=31:行=31÷7=4,列=31%7=3 → matrix[4][3]=95。和 target=104 比一下。
- 895 < 104:mid 这个数太小,它和它左边(更小的)11 个数全部排除 → 丢掉,lo 进到 32。
- 9取中点 mid=36:行=36÷7=5,列=36%7=1 → matrix[5][1]=110。和 target=104 比一下。
- 10110 > 104:mid 这个数已经太大,它和它右边(更大的)6 个数全部不可能是答案 → 丢掉,hi 收到 35。
- 11取中点 mid=33:行=33÷7=4,列=33%7=5 → matrix[4][5]=101。和 target=104 比一下。
- 12101 < 104:mid 这个数太小,它和它左边(更小的)2 个数全部排除 → 丢掉,lo 进到 34。
- 13取中点 mid=34:行=34÷7=4,列=34%7=6 → matrix[4][6]=104。和 target=104 比一下。
- 14matrix[4][6]=104 正好等于 target=104 ✓ 命中,返回 true。
- 15再演示「找不到」:target=87(它落在 86 和 89 之间,矩阵里没有这个数)。 把矩阵看成一维数组,下标范围 lo=0 .. hi=41(共 42 个数全部存活),开始二分。
- 16取中点 mid=20:行=20÷7=2,列=20%7=6 → matrix[2][6]=62。和 target=87 比一下。
- 1762 < 87:mid 这个数太小,它和它左边(更小的)21 个数全部排除 → 丢掉,lo 进到 21。
- 18取中点 mid=31:行=31÷7=4,列=31%7=3 → matrix[4][3]=95。和 target=87 比一下。
- 1995 > 87:mid 这个数已经太大,它和它右边(更大的)11 个数全部不可能是答案 → 丢掉,hi 收到 30。
- 20取中点 mid=25:行=25÷7=3,列=25%7=4 → matrix[3][4]=77。和 target=87 比一下。
- 2177 < 87:mid 这个数太小,它和它左边(更小的)5 个数全部排除 → 丢掉,lo 进到 26。
- 22取中点 mid=28:行=28÷7=4,列=28%7=0 → matrix[4][0]=86。和 target=87 比一下。
- 2386 < 87:mid 这个数太小,它和它左边(更小的)3 个数全部排除 → 丢掉,lo 进到 29。
- 24取中点 mid=29:行=29÷7=4,列=29%7=1 → matrix[4][1]=89。和 target=87 比一下。
- 2589 > 87:mid 这个数已经太大,它和它右边(更大的)2 个数全部不可能是答案 → 丢掉,hi 收到 28。
- 26搜索范围空了(lo=29 > hi=28),整张矩阵都比对过、没有等于 target=87 的数 → 返回 false。
⚠️ 容易写错的地方
✗ 错:mid 直接当行号或列号用
✓ 对:行 = mid / n、列 = mid % n
一维下标必须用列数 n 来还原行列,弄反会取错格子
✗ 错:换算时除以行数 m
✓ 对:除以、模的都是列数 n
一行有 n 个元素,所以是按 n 分组,不是 m
✗ 错:while 写成 lo < hi 漏掉相等
✓ 对:lo <= hi,且命中即返回
写 < 会漏掉 lo==hi 的最后一个候选,漏判
完整代码(Python / C++ / Java)
Python
def searchMatrix(matrix, target):
m, n = len(matrix), len(matrix[0])
lo, hi = 0, m * n - 1
while lo <= hi:
mid = (lo + hi) // 2
v = matrix[mid // n][mid % n] # 下标还原成行列
if v == target:
return True
elif v > target:
hi = mid - 1 # 丢右半
else:
lo = mid + 1 # 丢左半
return FalseC++
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
int m = matrix.size(), n = matrix[0].size();
int lo = 0, hi = m * n - 1;
while (lo <= hi) {
int mid = lo + (hi - lo) / 2;
int v = matrix[mid / n][mid % n]; // 下标还原行列
if (v == target) return true;
else if (v > target) hi = mid - 1; // 丢右半
else lo = mid + 1; // 丢左半
}
return false;
}
};Java
class Solution {
public boolean searchMatrix(int[][] matrix, int target) {
int m = matrix.length, n = matrix[0].length;
int lo = 0, hi = m * n - 1;
while (lo <= hi) {
int mid = lo + (hi - lo) / 2;
int v = matrix[mid / n][mid % n]; // 下标还原行列
if (v == target) return true;
else if (v > target) hi = mid - 1; // 丢右半
else lo = mid + 1; // 丢左半
}
return false;
}
}复杂度
时间
O(log(m·n))
对长度 m·n 的有序序列二分,每次范围减半
空间
O(1)
只用 lo / hi / mid 几个变量,原地查找
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 搜索二维矩阵 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
这题和 LeetCode 240 搜索二维矩阵 II 什么关系?+
240 每行升序、每列也升序,但少了「下一行第一个数大于上一行最后一个数」这条,整张表接起来不是一条有序链,没法拉直成一维二分。240 的做法是从右上角(或左下角)走 Z 字:当前值比 target 大就往左移一列、比 target 小就往下移一行,O(m+n)。74 多了行间接得上的强约束,才能整体当成有序数组二分到 O(log(m·n))。
循环条件为什么是 lo<=hi 而不是 lo+
二分收缩到 lo 和 hi 相等时,范围里还剩最后一格没比过。写成 lohi,也就是范围空了——target=87 那轮末尾 lo=29 超过 hi=28,正是靠这个条件停下返回 false。
一定要拉平成一维吗,逐行二分不行?+
逐行二分也能做对:先按每行首末判 target 落在哪一行,再在那行里二分,或者干脆对每一行各二分一次。前者是 O(log m + log n),后者 O(m·log n)。都能过,但整体拉平成一维二分只有一层循环、写法最短,而且天然是 O(log(m·n)),正好卡中题目给的复杂度要求。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 搜索二维矩阵 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。