题目描述

✅ 剑指 Offer 04. 二维数组中的查找

image-20261001225023007

image-20260928194053315

image-20260928194053316

image-20260928194053317

image-20260928194053318

题意分析

二维矩阵的每一行从左到右有序,每一列从上到下有序,判断给定目标值是否出现,返回布尔值。矩阵可以不是方阵,行数与列数要分别处理。

题目只保证行内和列内的大小顺序,不保证上一行末尾小于下一行开头,因此按行拼成一维数组后未必有序,不能直接对整个展开结果二分。空矩阵或没有列时,目标不存在。

解法:右上角阶梯搜索

核心思路

[!blue]

从右上角开始,当前元素同时是候选区域第一行的最大值和最后一列的最小值。这两个方向的大小关系相反,比较目标后就能确定排除哪一整条边界,而不是猜测应该往哪里走。

用 row、col 表示当前右上角,尚未排除的区域是第 row 行到最后一行、第 0 列到第 col 列。若当前值大于目标,它下方同列元素都不小于当前值,也不可能是目标,因此这一整列都可以删除,令 col--。

若当前值小于目标,它左侧同行元素都不大于当前值,同样不可能命中,因此这一整行都可以删除,令 row++。若等于目标则立即成功。每种删除都只排除已证明不含目标的位置,剩余区域仍然是行列有序的矩形,因此可以重复同样的判断。

每一步至少减少一行或一列,游标只会向左、向下移动。越过最左列或最后一行时,候选区域已经为空,可以确定不存在目标。从左下角出发也具有对称的排除关系;左上角则同时小于右侧和下方元素,无法仅凭一次比较确定删除哪一边。

解题步骤

  1. 先检查外层行数,再检查首行列数;任意一个为零都返回 false。
  2. 令 row = 0、col = 列数 - 1,从右上角开始。
  3. 当前位置等于目标时返回 true;大于目标时删除当前列、向左移动,小于目标时删除当前行、向下移动。
  4. 只要行列下标仍在候选范围内就继续比较,越界后返回 false。

代码实现

class Solution {
    public boolean findNumberIn2DArray(int[][] matrix, int target) {
        if (matrix.length == 0 || matrix[0].length == 0) {
            return false;
        }

        int row = 0;
        int col = matrix[0].length - 1;

        while (row < matrix.length && col >= 0) {
            if (matrix[row][col] == target) {
                return true;
            } else if (matrix[row][col] > target) {
                // 当前位置比目标大,其下方同列只会更大,整列可以排除。
                col--;
            } else {
                // 当前位置比目标小,其左侧同行只会更小,整行可以排除。
                row++;
            }
        }

        return false;
    }
}
func findNumberIn2DArray(matrix [][]int, target int) bool {
    if len(matrix) == 0 || len(matrix[0]) == 0 {
        return false
    }

    row := 0
    col := len(matrix[0]) - 1
    for row < len(matrix) && col >= 0 {
        if matrix[row][col] == target {
            return true
        } else if matrix[row][col] > target {
            // 当前位置比目标大,其下方同列只会更大,整列可以排除。
            col--
        } else {
            // 当前位置比目标小,其左侧同行只会更小,整行可以排除。
            row++
        }
    }
    return false
}

复杂度分析

  • 时间复杂度:$O(m + n)$,m、n 分别为行数和列数;行指针只下移、列指针只左移,累计移动次数不超过两者之和。
  • 空间复杂度:$O(1)$,只维护行、列两个下标。

关键点总结

[!green]

  • 选右上角是为了同时获得“左边更小”和“下面更大”的明确关系。
  • 每次比较排除一整行或一整列,并保持候选区域仍为矩形。
  • 只删除不可能命中的边界,既保证不漏解,也保证单向移动能够结束。
  • 行列分别有序与整个数组展开后有序是不同条件,不能混用搜索方法。

易错点总结

[!yellow]

  • 先读取 matrix[0] 再判断外层是否为空,空矩阵会直接越界。
  • 用行数初始化列下标,只在部分方阵输入上碰巧正确,非方阵会出错。
  • 把“大于目标左移、小于目标下移”写反,会删除仍可能包含答案的区域。
  • 从左上角遇到小于目标就随意选一个方向,不能证明另一个方向没有答案。
  • 直接展开成一维二分,额外假设了题目没有提供的跨行全局顺序。

相似题目

题目 难度 关联与区别
74. 搜索二维矩阵 中等 原题额外保证行与行整体有序,可以展平二分,本题只能依靠行列分别有序。
378. 有序矩阵中第 K 小的元素 中等 同样利用行列单调性,原题可通过阶梯计数与值域二分寻找第k小值。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2023/26553981
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!