📌 题目链接:240. 搜索二维矩阵 II - 力扣(LeetCode)
🔍 难度:中等 | 🏷️ 标签:数组、二分查找、矩阵、双指针
⏱️ 目标时间复杂度:O(m + n)
💾 空间复杂度:O(1)
🎯 核心算法:Z 字形搜索(从右上角开始的双指针遍历)
💡 面试重点:如何利用矩阵有序特性,避免暴力遍历?
🔥 考点深度:数据结构特性分析、空间优化、边界处理、逻辑推理能力
🧩 题目分析
我们面对的是一个 m × n 的二维矩阵,其具有以下两个关键性质:
✅ 每行从左到右升序排列
✅ 每列从上到下升序排列
这说明矩阵在 行方向和列方向都具备单调性,但整体并不是完全排序的(例如对角线可能不满足顺序),因此不能直接用一维二分。
🎯 目标:判断 target 是否存在于该矩阵中。
📌 典型输入示例:
matrix = [
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
], target = 5 → true
🔍 难点解析:
- 如果使用暴力法 O(mn),虽然能过,但不是最优解。
- 使用逐行二分查找 O(m log n),是合理优化。
- 但最巧妙的解法是利用 “右上角”或“左下角”的特殊性,实现 O(m+n) 的线性扫描!
🛠️ 核心算法及代码讲解
✅ Z 字形查找(从右上角出发)
🔍 思想本质:
🌀 利用“右上角元素是当前子矩阵的最大值(横向)且最小值(纵向)”这一特性,构建一个类似“Z”字路径的搜索策略。
我们从 右上角 (0, n-1) 开始,每次根据当前值与 target 的大小关系决定移动方向:
当前值 matrix[x][y] | 操作 |
|---|---|
| == target | 找到!返回 true |
| > target | 向左移(y--)→ 排除整列(因为该列所有值都更大) |
| < target | 向下移(x++)→ 排除整行(因为该行所有值都更小) |
👉 这样可以确保每一步都能排除掉至少一行或一列的数据,最终覆盖整个矩阵。
🧠 为什么选择右上角?
- 右上角是 本行最大值,也是 本列最小值
- 因此它是一个“决策点”,既能判断是否需要向下(比它大),也能判断是否需要向左(比它小)
- 左下角也可以(逻辑相反),但习惯上用右上角
✅ 算法流程图(文字版):
起始: (0, n-1)
while x < m and y >= 0:
if matrix[x][y] == target → return true
if matrix[x][y] > target → y--
else → x++
return false
🧭 解题思路(分步详解)
- 初始化起点:
x = 0,y = n - 1(右上角) - 循环条件:只要
x < m且y >= 0,就继续搜索 - 比较当前值:
- 若等于
target:立即返回true - 若大于
target:说明当前列的所有值都大于target(因为列递增),所以 向左跳过整列 - 若小于
target:说明当前行的所有值都小于target(因为行递增),所以 向下跳过整行
- 若等于
- 超出边界:若
x >= m或y < 0,说明未找到,返回false
🌟 关键洞察:每一次移动都是“排除不可能区域”,而不是盲目试探!
📊 算法分析
| 指标 | 分析 |
|---|---|
| 时间复杂度 | O(m + n) ✅ 最坏情况下走完一条对角线,最多走 m+n 步 |
| 空间复杂度 | O(1) ✅ 仅用常数额外空间 |
| 适用场景 | 适用于有行列单调性的二维矩阵搜索问题 |
| 面试加分项 | 能否想到“右上角”这个切入点?这是考察思维灵活性的重要标志 |
⚠️ 对比其他方法:
- 暴力遍历:O(mn) ❌ 不推荐
- 逐行二分:O(m log n) ✅ 可接受
- Z 字形查找:O(m+n) ✅ 最优解,体现对数据结构的理解深度
💻 代码(保留原模板,完整可运行)
#include <bits/stdc++.h>
using namespace std;
using ll = long long;
class Solution {
public:
bool searchMatrix(vector<vector<int>>& matrix, int target) {
int m = matrix.size(), n = matrix[0].size();
int x = 0, y = n - 1; // 从右上角开始
while (x < m && y >= 0) {
if (matrix[x][y] == target) {
return true; // 找到目标值
}
if (matrix[x][y] > target) {
--y; // 当前列全部大于 target,向左移动
} else {
++x; // 当前行全部小于 target,向下移动
}
}
return false; // 遍历完未找到
}
};
// 测试
signed main(){
ios::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
// 示例1
vector<vector<int>> matrix1 = {
{1, 4, 7, 11, 15},
{2, 5, 8, 12, 19},
{3, 6, 9, 16, 22},
{10, 13, 14, 17, 24},
{18, 21, 23, 26, 30}
};
int target1 = 5;
Solution sol;
cout << "Test 1: " << (sol.searchMatrix(matrix1, target1) ? "true" : "false") << endl; // true
// 示例2
int target2 = 20;
cout << "Test 2: " << (sol.searchMatrix(matrix1, target2) ? "true" : "false") << endl; // false
return 0;
}
🎯 面试拓展与常见提问
❓ Q1:为什么不能从左上角开始?
❌ 左上角是最小值,无法判断应往右还是往下。
✅ 例如:target=5,左上角是1,比它小 → 你不知道应该往右(可能更大)还是往下(也可能更大)。
✅ 但右上角是15,比5大 → 可以安全地向左排除一列。
❓ Q2:能否用二分查找解决?
✅ 可以,但不是最优!
每行做一次二分查找:O(m log n)
而 Z 字形是 O(m+n),当 m,n 很大时优势明显。
❓ Q3:有没有递归写法?
✅ 有,但会增加栈空间开销,不推荐。
原地迭代更高效、更清晰。
❓ Q4:如果矩阵没有行列有序呢?
❌ 那只能暴力或哈希表了。
✅ 本题的解法依赖于“行列单调性”这一前提。
📚 知识延伸:同类题目对比
| 题号 | 题名 | 特性 | 解法 |
|---|---|---|---|
| 74 | 搜索二维矩阵 | 行列有序,矩形连续 | 二分或 Z 字形 |
| 240 | 搜索二维矩阵 II | 行列有序,非连续 | Z 字形(最优) |
| 378 | 有序矩阵中第 K 小的元素 | 行列有序 | 二分答案 + 计数 |
| 230 | 二叉搜索树中第 K 小的元素 | 类似单调结构 | 中序遍历 |
🔁 规律总结:遇到“有序二维结构”,优先考虑 Z 字形、对角线、二分答案 等技巧!
🌟 本期完结,下期见!🔥
👉 点赞收藏加关注,新文更新不迷路。关注专栏【算法】LeetCode Hot100刷题日记,持续为你拆解每一道热题的底层逻辑与面试技巧!
💬 欢迎留言交流你的解法或疑问!一起进步,冲向 Offer!💪
📣 下一期预告:LeetCode 热题 100 第22题 —— 25.相交链表(简单)
🔹 题目:给你两个单链表的头节点
headA和headB,请你找出并返回两个链表相交的起始节点。如果两个链表没有交点,返回null。🔹 核心思路:双指针“同步走”技巧——当一个指针走到末尾时,跳到另一个链表头,最终在交点相遇(或同时为 null)。
🔹 考点:链表遍历、双指针、环形思想、数学对齐。
🔹 难度:简单,但思维巧妙,是面试高频题!
💡 关键洞察:
- 若两链表相交,则从交点开始,后续节点完全相同;
- 利用路径长度对齐(A + B = B + A),让两个指针在第二轮遍历时自然对齐。
⚠️ 注意:不能修改链表结构,也不能使用哈希表(虽然可行,但空间复杂度 O(n),非最优)!
📌 记住:当你在刷题时,不要只看答案,要像写这篇文章一样,深入思考每一步背后的原理、优化空间和面试价值。这才是真正提升算法能力的方式!
🎉 感谢阅读! 你的每一次点赞、收藏、转发,都是我坚持输出高质量内容的动力!🚀