【力扣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 == matrixi.length
  • 1 <= n, m <= 300
  • -10^9 <= matrixij <= 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++;
    }
}

三种情况:

情况 条件 动作 原因
找到目标 matrixlb == target return true 搜索成功
当前太大 matrixlb > target b-- 同行左边更小,目标可能在左边
当前太小 matrixlb < 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++ 向下搜索,直到找到或越界
为什么是 <= 而不是 <? 当 matrixlb < target 时行加一,等于 target 时在第一个 if 已经返回,else if 用 <= 或 < 等价

八、相关题目

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

相关推荐
yi0116 小时前
LeetCode 219:存在重复元素 II——哈希表记录“最近一次出现的位置”
数据结构·人工智能·笔记·python·算法·leetcode·哈希表
灯澜忆梦8 小时前
【面向对象编程C++】| 基础语法
java·c++·算法
小蒋观天下8 小时前
专项方案:大场景港口AI安防、多干扰环境下的算法调优与落地实操
人工智能·深度学习·算法·安全·机器学习·计算机视觉·ai大模型
Evand J8 小时前
【电机滤波例程5】渐消因子自适应扩展卡尔曼滤波(EKF),MATLAB。PMSM负载突变下的状态估计。附下载链接
算法·matlab
H.莓飛9 小时前
【数据结构】二叉树_OJ题
linux·开发语言·数据结构·算法
好想像大佬一样能够ak所有9 小时前
Leetcode35.搜索插入位置
数据结构·算法·leetcode
aqiu1111119 小时前
【C++算法打怪专栏】LeetCode 165. 比较版本号(双指针与字符串流法)
c++·算法·leetcode
黄金龙PLUS9 小时前
如何基于置换算法来设计对称加密算法
算法·网络安全·密码学·哈希算法·同态加密
不会就选b10 小时前
算法日常・每日刷题--<动态规划>14
算法
赛博守夜人10 小时前
跨境业务与出海合规之九:海外买量投放与营销虚假流量(Ad Fraud):跨国机房群控与归因欺诈识别算法
算法·安全