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

题目描述

给你一个满足下述两条属性的 m x n 整数矩阵:

  • 每行中的整数从左到右按非严格递增顺序排列
  • 每行的第一个整数大于前一行的最后一个整数

给你一个整数 target,如果 target 在矩阵中,返回 true;否则,返回 false。

示例 1:

复制代码
输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 3
输出:true

示例 2:

复制代码
输入:matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 13
输出:false

提示:

  • m == matrix.length
  • n == matrixi.length
  • 1 <= m, n <= 100
  • -10^4 <= matrixij, target <= 10^4

解题思路总览

方法 核心思想 时间复杂度 空间复杂度 特点
线性搜索(从左下角) 利用矩阵单调性,每次排除一行或一列 O(m+n) O(1) 最常用,面试推荐
线性搜索(从右上角) 同上,方向镜像 O(m+n) O(1) 与左下角等价
两次二分查找 先二分行,再二分列 O(log m + log n) O(1) 利用二维有序性
两次二分查找(优化) 虚拟数组展开成一维后二分 O(log(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 a = 0, b = m - 1;  // a: 列索引(从左向右),b: 行索引(从下向上)
        while (a < n && b >= 0) {
            if (matrix[b][a] == target) {
                return true;
            } else if (matrix[b][a] > target) {
                b--;  // 当前值大于 target,向上走(排除当前行)
            } else {
                a++;  // 当前值小于 target,向右走(排除当前列)
            }
        }
        return false;
    }
};

核心思想

从左下角元素开始搜索:

  • matrixba > target:说明 target 在当前行的上方(因为每列从上到下递增),b-- 向上移动
  • matrixba < target:说明 target 在当前列的右方(因为每行从左到右递增),a++ 向右移动

算法流程图

复制代码
以 matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 13 为例:

矩阵结构:
  列0  列1  列2  列3
行0  1   3   5   7
行1  10  11  16  20
行2  23  3034  60
        ^
      左下角 (b=2, a=0) = 23

搜索路径:
第1步:(2,0) = 23 > 13,向上 b=1
第2步:(1,0) = 10 < 13,向右 a=1
第3步:(1,1) = 11 < 13,向右 a=2
第4步:(1,2) = 16 > 13,向上 b=0
第5步:(0,2) = 5  < 13,向右 a=3
第6步:(0,3) = 7  < 13,a=4 越界
循环结束,返回 false

逐行解析

cpp 复制代码
int m = matrix.size();
int n = matrix[0].size();

获取矩阵的行数 m 和列数 n。


cpp 复制代码
int a = 0, b = m - 1;

初始化搜索起点为左下角:

  • a = 0 表示从第 0 列(最左列)开始
  • b = m - 1 表示从最后一行(最底行)开始

cpp 复制代码
while (a < n && b >= 0) {

循环条件:列索引未越右边界 且 行索引未越上边界。


cpp 复制代码
if (matrix[b][a] == target) {
    return true;
}

找到目标值,返回 true。


cpp 复制代码
} else if (matrix[b][a] > target) {
    b--;
}

当前值大于目标值:由于每列从上到下递增,当前值是当前列中的最小值(但仍大于 target),说明目标不可能在当前行或更下的行,向上移动一行。


cpp 复制代码
} else {
    a++;
}

当前值小于目标值:由于每行从左到右递增,当前值是当前行中的最小值(但仍小于 target),说明目标不可能在当前列或更左的列,向右移动一列。


cpp 复制代码
return false;

搜索完所有可能的行列组合仍未找到,返回 false。

复杂度分析

复杂度 分析
时间 最多移动 m + n 次,每次移动排除一行或一列,不会回头
空间 仅使用常数个变量(m, n, a, b),空间复杂度 O(1)

方法二:线性搜索(从右上角出发)

代码实现

cpp 复制代码
class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        int m = matrix.size();
        int n = matrix[0].size();
        int a = n - 1, b = 0;  // a: 列索引(从右向左),b: 行索引(从上向下)
        while (a >= 0 && b < m) {
            if (matrix[b][a] == target) {
                return true;
            } else if (matrix[b][a] > target) {
                a--;  // 当前值大于 target,向左走(排除当前列)
            } else {
                b++;  // 当前值小于 target,向下走(排除当前行)
            }
        }
        return false;
    }
};

与方法一的对比

对比项 方法一(左下角) 方法二(右上角)
起始位置 (m-1, 0) 左下角 (0, n-1) 右上角
当前值 > target b--(向上) a--(向左)
当前值 < target a++(向右) b++(向下)
搜索路径 先上后右 先左后下

算法流程图

复制代码
以 matrix = [[1,3,5,7],[10,11,16,20],[23,30,34,60]], target = 13 为例:

矩阵结构:
  列0  列1  列2  列3
行0  1   3   5   7
行1  10  11  16  20
行2  23  3034  60
              ^
            右上角 (b=0, a=3) = 7

搜索路径:
第1步:(0,3) = 7  < 13,向下 b=1
第2步:(1,3) = 20 > 13,向左 a=2
第3步:(1,2) = 16 > 13,向左 a=1
第4步:(1,1) = 11 < 13,向下 b=2
第5步:(2,1) = 30 > 13,向左 a=0
第6步:(2,0) = 23 > 13,向上 b=1
       b=1, a=0,回到之前位置,形成循环
       实际在第5步会发现 a<0,循环结束
返回 false

逐行解析

cpp 复制代码
int a = n - 1, b = 0;

初始化搜索起点为右上角:

  • a = n - 1 表示从第 n-1 列(最右列)开始
  • b = 0 表示从第 0 行(最顶行)开始

cpp 复制代码
while (a >= 0 && b < m) {

循环条件:列索引未越左边界 且 行索引未越下边界。


cpp 复制代码
if (matrix[b][a] == target) {
    return true;
} else if (matrix[b][a] > target) {
    a--;
} else {
    b++;
}

核心逻辑与左下角版本对称:

  • 当前值大于 target:由于每列从上到下递增,当前值是当前列中的最小值,说明目标不可能在当前列,向左移动
  • 当前值小于 target:由于每行从左到右递增,当前值是当前行中的最大值,说明目标不可能在当前行,向下移动

复杂度分析

复杂度 分析
时间 最多移动 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();
        
        // 第一步:二分查找行,找到第一个大于等于 target 的行
        int top = 0, bottom = m - 1;
        int targetRow = -1;
        while (top <= bottom) {
            int mid = top + (bottom - top) / 2;
            if (matrix[mid][0] <= target && target <= matrix[mid][n - 1]) {
                // target 在这一行内
                targetRow = mid;
                break;
            } else if (matrix[mid][0] > target) {
                bottom = mid - 1;
            } else {
                top = mid + 1;
            }
        }
        
        // 如果没有找到合适的行,返回 false
        if (targetRow == -1) {
            return false;
        }
        
        // 第二步:在找到的行内二分查找列
        int l = 0, r = n - 1;
        while (l <= r) {
            int mid = l + (r - l) / 2;
            if (matrix[targetRow][mid] == target) {
                return true;
            } else if (matrix[targetRow][mid] < target) {
                l = mid + 1;
            } else {
                r = mid - 1;
            }
        }
        
        return false;
    }
};

核心思想

利用矩阵的两个性质:

  1. 每行第一个元素大于前一行的最后一个元素
  2. 每行内部有序

先通过二分找到 target 可能所在的行,再在该行内二分查找 target。

复杂度分析

复杂度 分析
时间 O(log m + log n) = O(log(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, r = m * n - 1;
        while (l <= r) {
            int mid = l + (r - l) / 2;
            int midValue = matrix[mid / n][mid % n];
            if (midValue == target) {
                return true;
            } else if (midValue < target) {
                l = mid + 1;
            } else {
                r = mid - 1;
            }
        }
        
        return false;
    }
};

核心思想

将二维矩阵虚拟地展开成一维有序数组:

  • 二维坐标 (row, col) 映射到一维索引 idx = row * n + col
  • 一维索引 idx 对应的二维坐标为 (idx / n, idx % n)
  • 展开后仍保持有序,可直接二分

复杂度分析

复杂度 分析
时间 O(log(m*n))
空间 O(1)

边界情况分析

情况1:矩阵只有一行

复制代码
输入: matrix = [[1,3,5,7]], target = 5
分析: 从左下角 (0,0)=1 开始,向右找到 5
结果: true

情况2:矩阵只有一列

复制代码
输入: matrix = [[1],[3],[5],[7]], target = 5
分析: 从左下角 (3,0)=7 开始,向上找到 5
结果: true

情况3:target 小于所有元素

复制代码
输入: matrix = [[1,3,5,7],[10,11,16,20]], target = 0
分析: 从左下角 (1,0)=10 > 0,向上 (0,0)=1 > 0
     再向上 b=-1,循环结束
结果: false

情况4:target 大于所有元素

复制代码
输入: matrix = [[1,3,5,7],[10,11,16,20]], target = 100
分析: 从左下角 (1,0)=10 < 100,向右到 (1,1)=11 < 100
     继续向右直到越界
结果: false

情况5:target 正好在角落元素

复制代码
输入: matrix = [[1,3,5,7],[10,11,16,20]], target = 1
分析: 从左下角 (1,0)=10 > 1,向上 (0,0)=1 == 1
结果: true

面试追问 FAQ

问题 回答
为什么从左下角或右上角开始? 这两个位置是矩阵的"鞍点",可以保证每次移动都排除一行或一列,不会走回头路
为什么不能从左上角或右下角开始? 左上角:右移和下移都增大,无法排除;右下角:左移和上移都减小,无法排除
线性搜索的时间复杂度是多少? O(m+n),最多移动 m+n 步
如何处理 matrix 为空的情况? 需要先检查 matrix.size() > 0 && matrix0.size() > 0
方法三和方法四哪个更好? 本质相同,方法四写法更简洁;方法三思路更直观,适用于面试解释
如果每行不是严格递增怎么办? 本题条件是"非严格递增",即允许相等。上述算法仍然有效,因为比较操作符方向不变

相关题目

题目 难度 核心区别
74. 搜索二维矩阵(本题) 中等 行列均有单调性
240. 搜索二维矩阵 II 中等 每行每列均递增,允许从任意角开始
35. 搜索插入位置 简单 一维数组的搜索插入位置
34. 在排序数组中查找元素的第一个和最后一个位置 困难 二分查找左边界和右边界

总结

要点 说明
核心思想 利用矩阵的单调性,从特定角落出发,每次排除一行或一列
推荐起点 左下角或右上角
时间复杂度 O(m+n) 或 O(log(m*n))
空间复杂度 O(1)
关键性质 每行递增 + 每行首元素大于前一行尾元素 = 矩阵整体有序

相关推荐
仙俊红2 小时前
深入理解 ThreadLocal —— 从变量引用、强弱引用到 Spring Boot 实战
spring boot·python·算法
故渊at2 小时前
第五板块:Android 系统服务与电源管理 | 第十八篇:Battery Service 与 电量统计(Fuel Gauge)算法
android·算法·battery·电源·电池·电源管理·电量统计
The_Ticker2 小时前
港股量化实测:实时行情接口性能与数据质量深度解析
python·websocket·算法·金融
weisian1512 小时前
基础篇--概念原理-25-大模型的剪枝是什么?怎么理解?——从原理到实战,一篇讲透
算法·机器学习·大模型·剪枝
fie88892 小时前
基于有限体积法(FVM)的MATLAB流体力学求解程序
算法·matlab
小欣加油11 小时前
leetcode56 合并区间
c++·算法·leetcode·职场和发展
lqqjuly11 小时前
前沿算法深度解析(二)
人工智能·算法·机器学习
徐小夕12 小时前
万字长文!千万级文档 RAG 知识库系统落地实践
前端·算法·github
akunkuntaimei12 小时前
2026年高考数学各省真题及答案(完整版)
算法·高考