#LC240. 搜索二维矩阵 II

搜索二维矩阵 II

你好!我是教练。在攻克了“搜索二维矩阵 I”之后,我们迎来了它的进阶版——搜索二维矩阵 II

这一题在 NOI 竞赛中属于典型的**双指针(维护单调性)**应用。虽然它和第一题名字很像,但矩阵的性质发生了微妙的变化,导致我们的解法需要从“一维映射”转化为“单调边界移动”。


一、 题目描述 (NOI 竞赛格式)

题目名称:搜索二维矩阵 II (Search a 2D Matrix II) 文件输入matrix.in 文件输出matrix.out 时间限制:1.0s 内存限制:256MB

【问题描述】 编写一个高效的算法来搜索 m×nm \times n 矩阵 matrix 中的一个目标值 target 。该矩阵具有以下特性:

  1. 每行的元素从左到右升序排列。
  2. 每列的元素从上到下升序排列。

【输入格式】 第一行包含两个整数 mmnn,表示矩阵的行数和列数。 接下来 mm 行,每行包含 nn 个整数,表示矩阵的内容。 最后一行包含一个整数 targettarget

【输出格式】 如果目标值存在,输出字符串 true;否则,输出 false

【样例 1 输入】

5 5
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
5

【样例 1 输出】

true

【数据范围】

  • m==matrix.lengthm == matrix.length
  • n==matrix[i].lengthn == matrix[i].length
  • 1n,m3001 \le n, m \le 300
  • 109matrix[i][j],target109-10^9 \le matrix[i][j], target \le 10^9

二、 预备知识点

  1. 单调性搜索:利用行和列同时具备的递增性质。
  2. 双指针/排除法:通过当前值与目标值的比较,每次排除掉一行或一列。
  3. 决策点选择:在矩阵中寻找具有“分叉路口”特征的特殊位置。

三、 启发式思维引导

请拿出草稿纸,我们换一种思路来“切”这个矩阵。

第一步:观察性质的局限性 在第一题中,矩阵是全局有序的,像一根拉长的绳子。但在本题中,绳子断了,变成了每一行有序,每一列也有序

  • 思考:如果你站在左上角 (0,0)(0,0),发现 matrix[0][0] < target,你应该往右走还是往下走?
  • 反馈:右边比它大,下边也比它大。两个方向都有可能存在 target,这会产生递归分支,不够高效。

第二步:寻找特殊的“决策角” 我们要找一个位置,使得当我们比较 currenttarget 时,下一步的移动方向是唯一的。

  • 观察右上角 (0,n1)(0, n-1)
    • 它的左边元素全比它(行单调)。
    • 它的下边元素全比它(列单调)。
  • 问:如果 matrix[0][n-1] > target,target 可能在它的下方吗?
  • 答:不可能,因为下方只会更大。所以我们可以放心地排除掉最后一列
  • 问:如果 matrix[0][n-1] < target,target 可能在它的左方吗?
  • 答:不可能,因为左边只会更小。所以我们可以放心地排除掉第一行

第三步:模拟路径 在草稿纸上画一个矩形,从右上角开始,像下楼梯一样,要么向左移,要么向下移。每一次移动都把搜索范围缩小了一行或一列。

  • 这种走法被称为“Z字形搜索”或“阶梯搜索”。

四、 复杂度分析思考过程

  1. 时间复杂度

    • 我们从右上角出发,最远要走到左下角。
    • 每次比较,要么 row++,要么 col--
    • 最多移动 m+nm + n 次。
    • 复杂度:O(m+n)O(m + n)
    • 优化建议:对于 300×300300 \times 300 的规模,O(m+n)O(m+n) 仅需 600 次计算,而暴力法需要 90,000 次。这种线性增长的优势在 NN 很大时非常恐怖。
  2. 空间复杂度

    • 只需要维护两个指针 rc
    • 复杂度:O(1)O(1)

五、 算法流程图 (Mermaid 语法)

由于 Mermaid 中特殊字符容易报错,我将使用全文字描述逻辑:

graph TD
    Start(开始) --> GetMN(获取行数m和列数n)
    GetMN --> InitPointer(初始化指针: 行 r 等于 0, 列 c 等于 n 减 1)
    InitPointer --> LoopCond{r 小于 m 并且 c 大于等于 0}
    LoopCond -- 否 --> NotFound(输出 false 并结束)
    LoopCond -- 是 --> GetVal(获取当前位置值 val)
    GetVal --> Compare{val 与 target 的关系}
    
    Compare -- 等于 --> Found(输出 true 并结束)
    Compare -- 大于 --> MoveLeft(目标在左侧: c 减 1)
    Compare -- 小于 --> MoveDown(目标在下方: r 加 1)
    
    MoveLeft --> LoopCond
    MoveDown --> LoopCond

六、 读题关键词总结

  1. “每行升序” & “每列升序”

    • 关键词:同时满足两个维度的单调性。
    • 暗示:这通常对应 O(m+n)O(m+n) 的双指针解法,或者是对每一行进行二分查找的 O(mlogn)O(m \log n) 解法。
  2. “高效算法”

    • 暗示:题目暗示 O(m×n)O(m \times n) 的暴力遍历在更大的数据下可能无法通过,需要利用有序性。
  3. 对比“I”与“II”

    • 关键点:看有没有“下一行首大于上一行末”。如果有,用全局二分;如果没有,用阶梯搜索。

教练寄语: 在 NOI 竞赛中,右上角左下角往往是处理二维矩阵单调性问题的“神级起点”。这种“排除法”的思想不仅能解矩阵搜索,在很多寻找有序组合(如两数之和、三数之和)的问题中也有异曲同工之妙。多在草稿纸上画方向箭头,规律自然显现!