【力扣100题】25. 搜索二维矩阵 II

一、题目描述

编写一个高效的算法来搜索 m x n 矩阵 matrix 中的一个目标值 target。该矩阵具有以下特性:

  • 每行的元素从左到右升序排列
  • 每列的元素从上到下升序排列

示例 1:

复制代码
输入: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

示例 2:

复制代码
输入: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 = 20
输出:false

提示:

  • m == matrix.length
  • n == matrix[i].length
  • 1 <= n, m <= 300
  • -10^9 <= matrix[i][j] <= 10^9
  • 每行的所有元素从左到右升序排列
  • 每列的所有元素从上到下升序排列
  • -10^9 <= target <= 10^9

二、解题思路总览

核心思想:从右上角开始,利用有序性单向剪枝

从矩阵的右上角开始搜索,每次比较后根据大小关系排除一行或一列:

  • 如果当前元素大于 target,说明目标不可能在当前列的下方,往左走
  • 如果当前元素小于 target,说明目标不可能在当前行的上方,往下走

为什么从右上角或左下角开始?

因为这两个角具有「单向有序」的特性:

  • 右上角:同行左边更小,同列下边更大
  • 左下角:同行右边更大,同列上边更小

从其他角落开始无法保证单向剪枝。

方法 核心思路 时间复杂度 空间复杂度
搜索窗口法(本题) 从右上角开始,每次排除一行或一列 O(m + n) O(1)
二分搜索 对每行进行二分 O(m log n) O(1)
暴力搜索 遍历整个矩阵 O(m * n) O(1)

三、完整代码

cpp 复制代码
class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        int m = matrix.size();
        int n = matrix[0].size();

        int l = 0;      // 当前行,从 0 开始
        int b = n - 1;  // 当前列,从最后一列开始(右上角)

        while (l < m && b >= 0) {
            if (matrix[l][b] == target) {
                return true;
            }
            if (matrix[l][b] > target) {
                b--;  // 当前元素太大,目标在左边,列减一
            } else if (matrix[l][b] <= target) {
                l++;  // 当前元素太小,目标在下边,行加一
            }
        }

        return false;
    }
};

四、算法流程图

4.1 整体搜索流程

复制代码
输入:matrix, target

[Step 1] 初始化起点
         l = 0(从第一行开始)
         b = n - 1(从最后一列开始,即右上角)
         |
         v
[Step 2] 进入主循环
         |
         v
    l < m && b >= 0 ?
      |否
      v
    【返回 false】     ← 循环结束,未找到
      |
      |是
      v
[Step 3] 获取当前元素
         current = matrix[l][b]
         |
         v
    current == target ?
      |是
      v
    【返回 true】       ← 找到目标
      |
      |否
      v
    current > target ?
      |是                      |否
      v                       v
  [Step 4] 列左移           [Step 5] 行下移
      |                       |
      v                       v
  b--                     l++
      |                       |
      v                       v
  回到 Step 2              回到 Step 2

4.2 具体示例执行流程(找到目标)

复制代码
输入: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

初始:l=0, b=4

第一轮:
  matrix[0][4] = 15 > 5 → b-- → b=3

第二轮:
  matrix[0][3] = 11 > 5 → b-- → b=2

第三轮:
  matrix[0][2] = 7 > 5 → b-- → b=1

第四轮:
  matrix[0][1] = 4 < 5 → l++ → l=1

第五轮:
  matrix[1][1] = 5 == 5 → 返回 true

找到目标!共执行 5 步。

4.3 具体示例执行流程(未找到目标)

复制代码
输入:target = 20

初始:l=0, b=4

第一轮:matrix[0][4] = 15 < 20 → l++ → l=1
第二轮:matrix[1][4] = 19 < 20 → l++ → l=2
第三轮:matrix[2][4] = 22 > 20 → b-- → b=3
第四轮:matrix[2][3] = 16 < 20 → l++ → l=3
第五轮:matrix[3][3] = 17 < 20 → l++ → l=4
第六轮:matrix[4][3] = 26 > 20 → b-- → b=2
第七轮:matrix[4][2] = 23 > 20 → b-- → b=1
第八轮:matrix[4][1] = 21 > 20 → b-- → b=0
第九轮:matrix[4][0] = 18 < 20 → l++ → l=5
第十轮:l=5 不满足 l < m,循环退出

返回 false(未找到)

4.4 为什么从右上角开始

复制代码
以 4x4 矩阵为例:

     col=0  col=1  col=2  col=3
row=0  1      2      3      4
row=1  5      6      7      8
row=2  9     10     11     12
row=3 13     14     15     16

右上角 (l=0, b=3) = 4

特性:
- 往左走(b--):值变小(同行左边元素更小)
- 往下走(l++):值变大(同列下方元素更大)

这保证了每次比较后只能往一个方向走,不会丢失任何可能性。

如果从左上角开始 (l=0, b=0) = 1:
- 往右走(b++):值变大
- 往下走(l++):值变大
两个方向都可能导致 target,无法剪枝。

五、逐行解析

5.1 初始化起点

cpp 复制代码
int l = 0;      // 当前行
int b = n - 1;  // 当前列(右上角)

选择右上角的原因:

  • 同行左侧都是比它小的元素(往左走值变小)
  • 同列下方都是比它大的元素(往下走值变大)
  • 形成天然的搜索剪枝条件

5.2 主循环逻辑

cpp 复制代码
while (l < m && b >= 0) {
    if (matrix[l][b] == target) {
        return true;
    }
    if (matrix[l][b] > target) {
        b--;
    } else if (matrix[l][b] <= target) {
        l++;
    }
}

三种情况:

情况 条件 动作 原因
找到目标 matrix[l][b] == target return true 搜索成功
当前太大 matrix[l][b] > target b-- 同行左边更小,目标可能在左边
当前太小 matrix[l][b] < target l++ 同列下方更大,目标可能在下方

5.3 循环结束条件

cpp 复制代码
return false;

何时返回 false:

  • l >= m:已经越过最后一行(第 m 行不存在)
  • b < 0:已经越过第一列(第 -1 列不存在)
  • 两种情况都说明已经搜索完所有可能的路径

六、复杂度分析

指标 复杂度 说明
时间复杂度 O(m + n) 每次循环要么 l++,要么 b--,最多走 m+n 步
空间复杂度 O(1) 只用了两个指针变量

最坏情况演示:

复制代码
m=5, n=5 的矩阵,最多走 5+5-1=9 步:
l 从 0 到 4(5 步)
b 从 4 到 0(4 步)

七、面试追问

问题 回答要点
为什么从右上角开始而不是左上角? 左上角往右往下都变大,无法单向剪枝;右上角同行往左更小,同列往下更大
可以从左下角开始吗? 可以,逻辑相同:当前太大往上走(行减),当前太小往右走(列加)
时间复杂度为什么是 O(m+n)? 每次循环 l++ 或 b--,最多 m+n 步
如果 target 小于矩阵右上角? 从右上角开始,直接 b-- 向左搜索,直到找到或越界
为什么不用二分搜索? 每行每列虽然有序,但不是全局有序,二分只能作用在单行或单列上
最坏情况下搜索多少次? m+n-1 次,即沿着对角线走完整个矩阵
如果 target 大于矩阵右下角? 从右上角开始,一路 l++ 向下搜索,直到找到或越界
为什么是 <= 而不是 <? 当 matrix[l][b] < target 时行加一,等于 target 时在第一个 if 已经返回,else if 用 <= 或 < 等价

八、相关题目

题号 题目 关键点
240 搜索二维矩阵 II 本题
74 搜索二维矩阵 每行每列升序,但第一行最后一个元素的右边不再有更小的值
378 有序矩阵中第 K 小的元素 堆或二分搜索
54 螺旋矩阵 矩阵遍历

相关推荐
样例过了就是过了1 小时前
LeetCode热题100 多数元素
c++·算法·leetcode·贪心算法
白羊by1 小时前
YOLOv8 官方损失函数详解(按任务分类)
人工智能·深度学习·算法·yolo·分类
沪漂阿龙1 小时前
面试题:逻辑回归是什么?为什么用 Sigmoid、对数损失、最大似然、Softmax、多分类、类别不平衡一文讲透
人工智能·算法·机器学习·分类·逻辑回归
WL_Aurora1 小时前
备战蓝桥杯国赛【Day 9】
python·算法·蓝桥杯
洛水水1 小时前
【力扣100题】20.合并 K 个升序链表
算法·leetcode·链表
闻缺陷则喜何志丹1 小时前
【动态规划 前缀和】P7074 [CSP-J2020] 方格取数|普及+
c++·算法·前缀和·动态规划·洛谷
Liangwei Lin2 小时前
LeetCode 74. 搜索二维矩阵
算法·leetcode·矩阵
phltxy2 小时前
Redis Hash 数据类型:详解命令与实战场景
redis·算法·哈希算法
放羊郎10 小时前
基于ORB-SLAM2算法的优化工作
人工智能·算法·计算机视觉