搜索二维矩阵 II 图解题解
不用全扫,从右上角出发,每步必排一行或一列,O(m+n) 找到答案。
从地图右上角出发:那个角落的数,在它这一行是最大的、在它这一列是最小的。比 target 大就往左走(整列排除),比 target 小就往下走(整行排除)——每一步必然砍掉一整行或一整列,最多走 m+n 步就有结论。
这道题到底在问什么
- 输入
- target = 18
- 输出
- true(它在最后一行最左边)
- 输入
- target = 20
- 输出
- false(矩阵里没有这个数)
最优解:为什么这么做
一句话答案:LeetCode 240 搜索二维矩阵 II 的高效解法是从右上角出发的阶梯搜索:右上角元素同时是本行最大、本列最小,它比 target 大就左移排除一整列,比 target 小就下移排除一整行,每比较一次必砍掉一行或一列,最多走 m+n 步,时间 O(m+n)、空间 O(1)。
这道题真正在问什么
给一个 m 行 n 列的矩阵,每行从左到右升序、每列从上到下升序,判断 target 是否在其中。要小心它和「搜索二维矩阵 I」的区别:这里只有行、列各自有序,整体并不能摊平成一个有序的一维数组——上一行的末尾未必小于下一行的开头,所以把整个矩阵当一维序列做一次全局二分是行不通的。
暴力和逐行二分分别卡在哪
逐格扫描要 O(m×n),完全没用上有序性。稍好的做法是对每一行做二分查找,O(m·log n)——它用了行有序,却把列有序整个浪费了。两个维度的有序信息都在手上,理想的算法应该做到「一次比较就排除一大片」。于是问题变成:站在哪个位置比较,才能让「比 target 大」和「比 target 小」各自指向一个确定无疑的排除方向?
为什么偏偏要从右上角出发
右上角的元素有个独一无二的身份:它是所在行的最大值,同时是所在列的最小值。拿它和 target 比,两种结果都有干脆的结论——它比 target 大,说明它下方整列只会更大,这一列可以整列扔掉,指针左移;它比 target 小,说明连本行最大的都不够,这一行整行扔掉,指针下移。相比之下,左上角是行列双最小、右下角是行列双最大,比较结果无论偏大偏小都无法唯一确定该往哪走,一格都排除不了。左下角与右上角对称(本列最大、本行最小),同样可行。
每一步的排除为什么都不冤枉
这个走法可以看成拿单调性做贪心排除:每次比较后被扔掉的那一行或一列,有升序性质背书,绝不可能藏着 target;而 target 若存在,必然还留在剩余的子矩阵里——循环全程保持「候选区域始终包含答案(若答案存在)」这个不变量。指针只会向左或向下、永不回头,所以既不漏查也不绕圈,直到命中返回 true,或走出矩阵边界宣告不存在。
复杂度怎么数,边界在哪里
每一步要么列指针减一、要么行指针加一:列最多减 n 次、行最多加 m 次,总步数不超过 m+n,时间 O(m+n),空间 O(1),只用两个指针。实现上的两个坑:循环条件必须在指针越界(列减到负数、行加到 m)时停下并返回 false;比较后的移动方向别写反——比 target 大是左移砍列、比 target 小才是下移砍行,写反会把可能藏着答案的区域整片扔掉。
▶ 动画逐步走查(共 21 步)——想跟着动画一帧帧对照就展开
- 3记住右上角的妙处:它同时是「本行最大、本列最小」。比 target 大就往左砍一列,比 target 小就往下砍一行,相等就命中。
- 4指针放在右上角 (0,4),那一格的值是 15。准备拿它和 target=18 比一比。
- 5在 (0,4) 看到 15,和 target=18 比:它更小。它是这一行里最大的都还比 target 小,整行没戏,往下挪一格。
- 6刚才那一行整行排除(变灰),指针下移一行,来到 (1,4),值是 19,继续比。
- 7在 (1,4) 看到 19,和 target=18 比:它更大。它是这一列里最小的都已经比 target 大,整列没戏,往左挪一格。
- 8刚才那一列整列排除(变灰),指针左移一列,来到 (1,3),值是 12,继续比。
- 9在 (1,3) 看到 12,和 target=18 比:它更小。它是这一行里最大的都还比 target 小,整行没戏,往下挪一格。
- 10刚才那一行整行排除(变灰),指针下移一行,来到 (2,3),值是 16,继续比。
- 11在 (2,3) 看到 16,和 target=18 比:它更小。它是这一行里最大的都还比 target 小,整行没戏,往下挪一格。
- 12刚才那一行整行排除(变灰),指针下移一行,来到 (3,3),值是 17,继续比。
- 13在 (3,3) 看到 17,和 target=18 比:它更小。它是这一行里最大的都还比 target 小,整行没戏,往下挪一格。
- 14刚才那一行整行排除(变灰),指针下移一行,来到 (4,3),值是 26,继续比。
- 15在 (4,3) 看到 26,和 target=18 比:它更大。它是这一列里最小的都已经比 target 大,整列没戏,往左挪一格。
- 16刚才那一列整列排除(变灰),指针左移一列,来到 (4,2),值是 23,继续比。
- 17在 (4,2) 看到 23,和 target=18 比:它更大。它是这一列里最小的都已经比 target 大,整列没戏,往左挪一格。
- 18刚才那一列整列排除(变灰),指针左移一列,来到 (4,1),值是 21,继续比。
- 19在 (4,1) 看到 21,和 target=18 比:它更大。它是这一列里最小的都已经比 target 大,整列没戏,往左挪一格。
- 20刚才那一列整列排除(变灰),指针左移一列,来到 (4,0),值是 18,继续比。
- 21在 (4,0) 看到 18,和 target=18 比:正好相等,找到了!
- 22(4,0) 这一格就是 target=18(标绿),返回 true。一共走了 9 步就锁定了它。
- 23回看整趟:蓝色是走过比较过的格子,灰色是被整行整列排除的区域,绿色就是命中的 target。一共 9 步,远小于 5×5=25 格。
⚠️ 容易写错的地方
✗ 错:从左上角或右下角出发
✓ 对:从右上角(或左下角)出发
左上角是行列都最小、右下角是行列都最大,比 target 大或小都无法唯一决定往哪走;右上角「本行最大、本列最小」才能二选一
✗ 错:比较方向写反,大于时下移、小于时左移
✓ 对:大于 target 左移砍列,小于 target 下移砍行
右上角比 target 大说明整列都大(往下更大),只能往左;写反会漏掉正确区域
✗ 错:循环条件漏判越界,c 减到 -1 还在访问
✓ 对:while r < m 且 c >= 0
指针一直左移/下移可能走出矩阵,必须在越界前停下并返回 false
完整代码(Python / C++ / Java)
Python
def searchMatrix(matrix, target):
if not matrix or not matrix[0]:
return False
r, c = 0, len(matrix[0]) - 1 # 从右上角出发
while r < len(matrix) and c >= 0:
v = matrix[r][c]
if v == target:
return True
elif v > target:
c -= 1 # 太大,砍掉这一列
else:
r += 1 # 太小,砍掉这一行
return FalseC++
bool searchMatrix(vector<vector<int>>& m, int target){
if (m.empty() || m[0].empty()) return false;
int r = 0, c = m[0].size() - 1; // 右上角
while (r < (int)m.size() && c >= 0) {
int v = m[r][c];
if (v == target) return true;
else if (v > target) c--; // 砍列
else r++; // 砍行
}
return false;
}Java
public boolean searchMatrix(int[][] m, int target) {
if (m.length == 0 || m[0].length == 0) return false;
int r = 0, c = m[0].length - 1; // 右上角
while (r < m.length && c >= 0) {
int v = m[r][c];
if (v == target) return true;
else if (v > target) c--; // 砍列
else r++; // 砍行
}
return false;
}复杂度
时间
O(m+n)
每一步必定砍掉一整行或一整列,最多走 m+n 步
空间
O(1)
只用 r、c 两个指针,不开额外空间
看不够?换成动画再走一遍
上面的推演每一步都对应一帧动画。点开交互动画版,能一步步看着 搜索二维矩阵 II 的数据怎么变、指针怎么走,还能切 Python / Java / C++ 跟着练。
面试官可能追问
为什么时间复杂度是 O(m+n) 而不是 O(m×n)?+
因为指针只会往左或往下走,绝不回头。每走一步要么 c 减一(砍一列)要么 r 加一(砍一行),c 最多减 n 次、r 最多加 m 次,所以总步数不超过 m+n。
能不能对每一行做二分查找?复杂度多少?+
可以,对每行二分是 O(m·log n)。本题的右上角走法 O(m+n) 通常更优且更好写,也不需要每行都单独二分。
从左下角出发可以吗?+
可以,完全对称。左下角是「本列最大、本行最小」,比 target 大就上移(砍行),小就右移(砍列),逻辑镜像一致。
想听吴师兄把这道题讲给你听?
文字版和动画都随便看。开通图解算法年卡,可以听吴师兄把 搜索二维矩阵 II 一步步讲透(全站已上线 905 份讲透,持续扩充), 卡住的地方还有 AI 私教小欧就着动画帮你拆到懂。
本平台为独立第三方培训机构,与华为技术有限公司无任何关联;课程的服务内容与权益以购买协议为准,学习效果因个人情况而异。「华为 OD」「华为可信」等仅为对岗位与考试方向的客观描述,相关商标归各自权利人所有。