【Hot100|20-LeetCode 240. 搜索二维矩阵 II 】

LeetCode 240. 搜索二维矩阵 II - 完整解法详解

一、问题理解

问题描述

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

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

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

示例

python

复制代码
输入: 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
解释: 目标值 5 存在于矩阵中

输入: 同样的矩阵, target = 20
输出: false
解释: 目标值 20 不存在于矩阵中

要求

  • 时间复杂度尽可能低

  • 不使用额外空间

  • 矩阵不为空

二、核心思路:排除法(Z字形搜索)

基本思想

利用矩阵的行列有序特性,从矩阵的右上角(或左下角)开始搜索:

  • 从右上角开始:如果当前值等于 target,返回 true

  • 如果当前值小于 target:说明当前行所有值都小于 target(因为当前值是当前行最大的),排除当前行,向下移动

  • 如果当前值大于 target:说明当前列所有值都大于 target(因为当前值是当前列最小的),排除当前列,向左移动

为什么选择右上角?

  • 右上角的元素:是所在行的最大值,所在列的最小值

  • 左下角的元素:是所在行的最小值,所在列的最大值

  • 这两个位置都具有特殊的比较意义,可以用于快速排除行或列

三、代码逐行解析

方法一:右上角开始搜索(最优解)

Python 解法

python

复制代码
from typing import List

class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        # 获取矩阵的行数和列数
        m, n = len(matrix), len(matrix[0])
        
        # 从右上角开始搜索
        i, j = 0, n - 1  # i: 行索引, j: 列索引
        
        # 当索引在矩阵范围内时继续搜索
        while i < m and j >= 0:
            if matrix[i][j] == target:
                return True  # 找到目标值
            
            if matrix[i][j] < target:
                # 当前值小于目标值,说明这一行所有值都小于目标值
                # 排除当前行,向下移动
                i += 1
            else:
                # 当前值大于目标值,说明这一列所有值都大于目标值
                # 排除当前列,向左移动
                j -= 1
        
        # 搜索完整个矩阵都没有找到
        return False
Java 解法

java

复制代码
class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        // 获取矩阵的行数和列数
        int m = matrix.length, n = matrix[0].length;
        
        // 从右上角开始
        int i = 0, j = n - 1;
        
        // 在矩阵范围内搜索
        while (i < m && j >= 0) {
            if (matrix[i][j] == target) {
                return true;  // 找到目标值
            }
            
            if (matrix[i][j] < target) {
                // 当前值小于目标值,排除当前行
                i++;
            } else {
                // 当前值大于目标值,排除当前列
                j--;
            }
        }
        
        // 未找到
        return false;
    }
}

方法二:左下角开始搜索(同样有效)

Python 解法

python

复制代码
from typing import List

class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        m, n = len(matrix), len(matrix[0])
        
        # 从左下角开始搜索
        i, j = m - 1, 0  # i: 行索引, j: 列索引
        
        while i >= 0 and j < n:
            if matrix[i][j] == target:
                return True
            
            if matrix[i][j] < target:
                # 当前值小于目标值,说明这一列所有值都小于目标值
                # 排除当前列,向右移动
                j += 1
            else:
                # 当前值大于目标值,说明这一行所有值都大于目标值
                # 排除当前行,向上移动
                i -= 1
        
        return False
Java 解法

java

复制代码
class Solution {
    public boolean searchMatrix(int[][] matrix, int target) {
        int m = matrix.length, n = matrix[0].length;
        
        // 从左下角开始
        int i = m - 1, j = 0;
        
        while (i >= 0 && j < n) {
            if (matrix[i][j] == target) {
                return true;
            }
            
            if (matrix[i][j] < target) {
                // 排除当前列
                j++;
            } else {
                // 排除当前行
                i--;
            }
        }
        
        return false;
    }
}

四、Java 与 Python 语法对比

1. 矩阵维度获取

操作 Java Python
获取行数 matrix.length len(matrix)
获取列数 matrix[0].length len(matrix[0])

2. 循环控制

操作 Java Python
while 循环 while (i < m && j >= 0) while i < m and j >= 0:
自增/自减 i++, j-- i += 1, j -= 1

3. 条件判断

操作 Java Python
相等比较 matrix[i][j] == target matrix[i][j] == target
小于比较 matrix[i][j] < target matrix[i][j] < target

五、实例演示

示例矩阵:

text

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

方法一(右上角开始)步骤:

  1. 初始位置i=0, j=4,值为 matrix[0][4] = 15

  2. 比较15 > 5,所以当前列(第5列)所有值都大于5,排除第5列,j=3

  3. 新位置i=0, j=3,值为 matrix[0][3] = 11

  4. 比较11 > 5,排除第4列,j=2

  5. 新位置i=0, j=2,值为 matrix[0][2] = 7

  6. 比较7 > 5,排除第3列,j=1

  7. 新位置i=0, j=1,值为 matrix[0][1] = 4

  8. 比较4 < 5,说明第1行所有值都小于5,排除第1行,i=1

  9. 新位置i=1, j=1,值为 matrix[1][1] = 5

  10. 比较5 == 5,找到目标值,返回 True

搜索路径可视化:

text

复制代码
初始: (0,4)=15 > 5 → 排除第5列
      (0,3)=11 > 5 → 排除第4列
      (0,2)=7 > 5 → 排除第3列
      (0,1)=4 < 5 → 排除第1行
      (1,1)=5 == 5 → 找到!

方法二(左下角开始)步骤:

  1. 初始位置i=4, j=0,值为 matrix[4][0] = 18

  2. 比较18 > 5,说明第5行所有值都大于5,排除第5行,i=3

  3. 新位置i=3, j=0,值为 matrix[3][0] = 10

  4. 比较10 > 5,排除第4行,i=2

  5. 新位置i=2, j=0,值为 matrix[2][0] = 3

  6. 比较3 < 5,说明第1列所有值都小于5,排除第1列,j=1

  7. 新位置i=2, j=1,值为 matrix[2][1] = 6

  8. 比较6 > 5,排除第3行,i=1

  9. 新位置i=1, j=1,值为 matrix[1][1] = 5

  10. 比较5 == 5,找到目标值,返回 True

六、关键细节解析

1. 为什么这个方法有效?

  • 利用矩阵的行列有序特性

  • 每次比较都能排除一行或一列

  • 搜索路径类似于"楼梯形"或"Z字形"

2. 时间复杂度是多少?

  • 最坏情况下:从右上角到左下角,需要移动 m+n 步

  • 时间复杂度:O(m+n)

  • 这比暴力搜索的 O(m×n) 快得多

3. 空间复杂度是多少?

  • 只使用了常数个额外变量

  • 空间复杂度:O(1)

4. 为什么不能从左上角或右下角开始?

  • 左上角:是最小值,如果 target 更大,不知道应该向下还是向右

  • 右下角:是最大值,如果 target 更小,不知道应该向上还是向左

  • 右上角和左下角具有"一边最大/最小"的特性,可以明确排除方向

5. 如何处理空矩阵?

  • 题目保证矩阵不为空

  • 但实际编程中可以添加边界检查:

python

复制代码
if not matrix or not matrix[0]:
    return False

七、复杂度分析

方法一/方法二(Z字形搜索)

  • 时间复杂度:O(m + n)

    • 最坏情况下需要遍历一行和一列的所有元素

    • 每一步排除一行或一列

  • 空间复杂度:O(1)

    • 只使用了常数个额外变量

对比其他方法:

方法 时间复杂度 空间复杂度 备注
暴力搜索 O(m×n) O(1) 遍历所有元素
二分查找每行 O(m log n) O(1) 对每行进行二分查找
Z字形搜索 O(m+n) O(1) 最优解

八、其他解法

解法一:二分查找每行

python

复制代码
from typing import List
import bisect

class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        # 对每一行进行二分查找
        for row in matrix:
            # 使用bisect模块进行二分查找
            idx = bisect.bisect_left(row, target)
            if idx < len(row) and row[idx] == target:
                return True
        return False

解法二:分治法(递归)

python

复制代码
from typing import List

class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        def search_submatrix(top, bottom, left, right):
            # 递归终止条件
            if top > bottom or left > right:
                return False
            
            # 选择中间行和中间列
            mid_row = (top + bottom) // 2
            mid_col = (left + right) // 2
            
            mid_val = matrix[mid_row][mid_col]
            
            if mid_val == target:
                return True
            elif mid_val < target:
                # 目标值可能在右下部分或右上部分或左下部分
                return (search_submatrix(mid_row + 1, bottom, left, right) or
                        search_submatrix(top, mid_row, mid_col + 1, right))
            else:
                # 目标值可能在左上部分或右上部分或左下部分
                return (search_submatrix(top, mid_row - 1, left, right) or
                        search_submatrix(top, bottom, left, mid_col - 1))
        
        m, n = len(matrix), len(matrix[0])
        return search_submatrix(0, m - 1, 0, n - 1)

解法三:暴力搜索(不推荐)

python

复制代码
from typing import List

class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        # 遍历所有元素
        for row in matrix:
            for val in row:
                if val == target:
                    return True
        return False

九、常见问题与解答

Q1: 如果矩阵非常大怎么办?

A1: Z字形搜索的时间复杂度是 O(m+n),即使对于非常大的矩阵也非常高效。例如 1000×1000 的矩阵最多只需要 2000 次比较。

Q2: 这个方法适用于所有有序矩阵吗?

A2: 这个方法适用于题目描述的特定有序矩阵(每行从左到右升序,每列从上到下升序)。对于其他类型的排序矩阵可能需要不同的方法。

Q3: 如果我想返回目标值的位置怎么办?

A3: 可以修改算法,在找到目标值时返回其坐标:

python

复制代码
def searchMatrixWithPosition(self, matrix: List[List[int]], target: int) -> tuple:
    m, n = len(matrix), len(matrix[0])
    i, j = 0, n - 1
    
    while i < m and j >= 0:
        if matrix[i][j] == target:
            return (i, j)  # 返回坐标
        elif matrix[i][j] < target:
            i += 1
        else:
            j -= 1
    
    return (-1, -1)  # 未找到

Q4: 如何处理有重复元素的矩阵?

A4: 算法仍然有效。如果存在重复元素,找到其中一个就会返回 true。如果需要找到所有出现位置,可以在找到后继续搜索,但这样时间复杂度会变高。

Q5: 为什么这个方法比二分查找每行更好?

A5:

  • 二分查找每行:O(m log n)

  • Z字形搜索:O(m + n)

  • 当 m 和 n 都很大时,O(m+n) 比 O(m log n) 更优

  • 例如:1000×1000 矩阵,二分查找需要约 1000×10=10000 次比较,而 Z字形搜索最多需要 2000 次比较

十、相关题目

1. LeetCode 74. 搜索二维矩阵

python

复制代码
from typing import List

class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        # 这个矩阵的特点是:每行有序,且下一行第一个大于上一行最后一个
        # 可以将其视为一个一维有序数组进行二分查找
        
        if not matrix or not matrix[0]:
            return False
        
        m, n = len(matrix), len(matrix[0])
        left, right = 0, m * n - 1
        
        while left <= right:
            mid = (left + right) // 2
            # 将一维索引转换为二维坐标
            row = mid // n
            col = mid % n
            
            if matrix[row][col] == target:
                return True
            elif matrix[row][col] < target:
                left = mid + 1
            else:
                right = mid - 1
        
        return False

2. LeetCode 378. 有序矩阵中第K小的元素

python

复制代码
from typing import List
import heapq

class Solution:
    def kthSmallest(self, matrix: List[List[int]], k: int) -> int:
        # 使用最小堆,归并排序的思想
        n = len(matrix)
        heap = []
        
        # 将每行的第一个元素放入堆中
        for i in range(min(k, n)):
            heapq.heappush(heap, (matrix[i][0], i, 0))
        
        # 弹出k-1次,第k次弹出的就是第k小的元素
        for _ in range(k - 1):
            val, row, col = heapq.heappop(heap)
            # 如果当前行还有下一个元素,将其加入堆中
            if col + 1 < n:
                heapq.heappush(heap, (matrix[row][col + 1], row, col + 1))
        
        return heap[0][0]

十一、总结

核心要点

  1. 利用有序特性:矩阵的行和列都有序,可以用于快速排除

  2. 选择合适的起点:右上角或左下角具有特殊的比较意义

  3. 排除法:每次比较可以排除一整行或一整列

算法步骤(右上角法)

  1. 初始化指针到右上角 (0, n-1)

  2. 当指针在矩阵范围内:

    • 如果当前值等于目标值,返回 true

    • 如果当前值小于目标值,向下移动(排除当前行)

    • 如果当前值大于目标值,向左移动(排除当前列)

  3. 如果搜索结束未找到,返回 false

时间复杂度与空间复杂度

  • 时间复杂度:O(m + n),最坏情况下遍历一行和一列

  • 空间复杂度:O(1),只使用常数个额外变量

适用场景

  • 行列都有序的矩阵搜索问题

  • 需要高效搜索的大型矩阵

  • 不能使用额外空间的场景

扩展思考

Z字形搜索法展示了如何利用数据结构的特殊性质设计高效算法。这种思想可以应用于:

  • 其他类型的二维搜索问题

  • 多维数据的搜索

  • 利用数据特性优化算法的问题

掌握这种搜索技巧不仅能够解决特定的矩阵搜索问题,还能提高对算法优化的理解,是面试中常见的高级算法题目。

相关推荐
zmzb01032 小时前
C++课后习题训练记录Day85
开发语言·c++·算法
2301_822366352 小时前
C++中的协程编程
开发语言·c++·算法
m0_736919102 小时前
C++中的事件驱动编程
开发语言·c++·算法
wenyi_leo2 小时前
强大的claude code
linux·运维·服务器
zmjjdank1ng2 小时前
Vim是什么?Vim和vi有什么关系
linux·编辑器·vim
嵌入小生0072 小时前
数据结构基础内容 + 顺序表 + 单链表的学习---嵌入式入门---Linux
linux·数据结构·学习·算法·小白·嵌入式软件
hweiyu002 小时前
Linux 命令:join
linux
知无不研2 小时前
Linux下socket网络编程
linux·运维·网络·后端·socket编程
xhbaitxl2 小时前
算法学习day30-贪心算法
学习·算法·贪心算法