LeetCode第74题:搜索二维矩阵

94 阅读6分钟

LeetCode第74题:搜索二维矩阵

题目描述

编写一个高效的算法来判断 m x n 矩阵中,是否存在一个目标值。该矩阵具有以下特性:

  • 每行中的整数从左到右按升序排列。
  • 每行的第一个整数大于前一行的最后一个整数。

难度

中等

问题链接

搜索二维矩阵

示例

示例 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 == matrix[i].length
  • 1 <= m, n <= 100
  • -10^4 <= matrix[i][j], target <= 10^4

解题思路

这道题目可以利用矩阵的特性,使用二分查找来高效解决。由于矩阵中的元素是有序的,且每行的第一个元素大于前一行的最后一个元素,所以整个矩阵可以看作是一个排序好的一维数组,只是在视觉上被分成了多行。

方法一:二维坐标转换为一维坐标的二分查找

  1. 将二维矩阵看作是一个排序好的一维数组,长度为 m * n
  2. 对这个虚拟的一维数组进行二分查找
  3. 在二分查找过程中,需要将一维索引转换为二维坐标:
    • 对于一维索引 idx,对应的二维坐标为 (idx / n, idx % n),其中 n 是矩阵的列数

方法二:两次二分查找

  1. 首先对矩阵的第一列进行二分查找,确定目标值可能在哪一行
  2. 然后在确定的行中进行二分查找,查找目标值

关键点

  • 理解矩阵的有序特性,将二维问题转化为一维问题
  • 熟练应用二分查找算法
  • 正确处理二维坐标和一维索引之间的转换

算法步骤分析

以方法一为例,我们将二维矩阵视为一维数组,然后进行二分查找:

步骤操作说明
1初始化设置左指针 left = 0,右指针 right = m * n - 1
2二分查找left <= right 时,计算中间索引 mid = (left + right) / 2
3坐标转换将一维索引 mid 转换为二维坐标 (mid / n, mid % n)
4比较元素比较矩阵中对应坐标的元素与目标值
5调整指针根据比较结果调整 leftright 指针
6返回结果如果找到目标值返回 true,否则返回 false

算法可视化

对于矩阵 [[1,3,5,7],[10,11,16,20],[23,30,34,60]],查找目标值 3

  1. 初始状态:left = 0, right = 11
  2. 第一次迭代:mid = 5,对应坐标 (1, 1),值为 11,大于目标值 3,所以 right = 4
  3. 第二次迭代:mid = 2,对应坐标 (0, 2),值为 5,大于目标值 3,所以 right = 1
  4. 第三次迭代:mid = 0,对应坐标 (0, 0),值为 1,小于目标值 3,所以 left = 1
  5. 第四次迭代:mid = 1,对应坐标 (0, 1),值为 3,等于目标值 3,返回 true

代码实现

C# 实现

public class Solution {
    public bool SearchMatrix(int[][] matrix, int target) {
        int m = matrix.Length;
        int n = matrix[0].Length;
        
        // 将二维矩阵视为一维数组进行二分查找
        int left = 0;
        int right = m * n - 1;
        
        while (left <= right) {
            int mid = left + (right - left) / 2;
            // 将一维索引转换为二维坐标
            int row = mid / n;
            int col = mid % n;
            
            if (matrix[row][col] == target) {
                return true;
            } else if (matrix[row][col] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        
        return false;
    }
}

Python 实现

class Solution:
    def searchMatrix(self, matrix: List[List[int]], target: int) -> bool:
        m, n = len(matrix), len(matrix[0])
        
        # 将二维矩阵视为一维数组进行二分查找
        left, right = 0, m * n - 1
        
        while left <= right:
            mid = (left + right) // 2
            # 将一维索引转换为二维坐标
            row, col = mid // n, mid % n
            
            if matrix[row][col] == target:
                return True
            elif matrix[row][col] < target:
                left = mid + 1
            else:
                right = mid - 1
        
        return False

C++ 实现

class Solution {
public:
    bool searchMatrix(vector<vector<int>>& matrix, int target) {
        int m = matrix.size();
        int n = matrix[0].size();
        
        // 将二维矩阵视为一维数组进行二分查找
        int left = 0;
        int right = m * n - 1;
        
        while (left <= right) {
            int mid = left + (right - left) / 2;
            // 将一维索引转换为二维坐标
            int row = mid / n;
            int col = mid % n;
            
            if (matrix[row][col] == target) {
                return true;
            } else if (matrix[row][col] < target) {
                left = mid + 1;
            } else {
                right = mid - 1;
            }
        }
        
        return false;
    }
};

执行结果

C# 执行结果

  • 执行用时:84 ms,击败了 95.24% 的 C# 提交
  • 内存消耗:40.1 MB,击败了 85.71% 的 C# 提交

Python 执行结果

  • 执行用时:32 ms,击败了 93.15% 的 Python3 提交
  • 内存消耗:16.8 MB,击败了 87.42% 的 Python3 提交

C++ 执行结果

  • 执行用时:0 ms,击败了 100.00% 的 C++ 提交
  • 内存消耗:9.4 MB,击败了 89.65% 的 C++ 提交

代码亮点

  1. 一维二分查找的应用:通过将二维矩阵转换为一维数组,简化了问题,使用标准的二分查找算法解决。
  2. 坐标转换:使用整除和取模运算,高效地在一维索引和二维坐标之间进行转换。
  3. 边界处理:在计算中间索引时,使用 left + (right - left) / 2 而不是 (left + right) / 2,避免整数溢出。
  4. 代码简洁:三种语言的实现都保持了代码的简洁性和可读性,逻辑清晰。

常见错误分析

  1. 坐标转换错误:在将一维索引转换为二维坐标时,容易混淆行和列的计算方式。
  2. 二分查找边界条件:二分查找的循环条件和指针更新容易出错,需要注意是 left <= right 还是 left < right
  3. 没有利用矩阵特性:如果不利用矩阵的有序特性,可能会使用 O(m*n) 的暴力搜索,效率低下。
  4. 整数溢出:在计算中间索引时,如果直接使用 (left + right) / 2,当 leftright 都很大时可能会导致整数溢出。

解法比较

解法时间复杂度空间复杂度优点缺点
一维二分查找O(log(m*n))O(1)实现简单,直接利用二分查找需要进行坐标转换
两次二分查找O(log m + log n)O(1)分解问题,先确定行再确定列实现稍复杂,需要两次二分查找
暴力搜索O(m*n)O(1)实现最简单效率低下,不利用矩阵特性

相关题目