当前位置: 首页 > news >正文

Java详解LeetCode 热题 100(21):LeetCode 240. 搜索二维矩阵 II(Search a 2D Matrix II)详解

文章目录

    • 1. 题目描述
    • 2. 理解题目
      • 2.1 矩阵特性分析
      • 2.2 关键观察
    • 3. 解法一:从右上角开始搜索(推荐)
      • 3.1 思路分析
      • 3.2 图解演示
      • 3.3 Java代码实现
      • 3.4 代码详解
      • 3.5 复杂度分析
      • 3.6 适用场景
    • 4. 解法二:从左下角开始搜索
      • 4.1 思路分析
      • 4.2 Java代码实现
      • 4.3 图解演示
      • 4.4 复杂度分析
    • 5. 解法三:逐行二分搜索
      • 5.1 思路分析
      • 5.2 Java代码实现
      • 5.3 代码详解
      • 5.4 复杂度分析
      • 5.5 优化版本:智能剪枝
    • 6. 解法四:分治法
      • 6.1 思路分析
      • 6.2 Java代码实现
      • 6.3 复杂度分析
    • 7. 详细步骤分析与示例跟踪
      • 7.1 示例 1:目标值存在的情况
      • 7.2 示例 2:目标值不存在的情况
      • 7.3 边界情况分析
    • 8. 常见错误与优化
      • 8.1 常见错误
      • 8.2 性能优化
    • 9. 扩展题目与应用
      • 9.1 相关LeetCode题目
      • 9.2 实际应用场景
    • 10. 完整的Java解决方案
      • 10.1 多种解法的性能对比
    • 11. 总结与技巧
      • 11.1 解题要点
      • 11.2 算法选择建议
      • 11.3 面试技巧
      • 11.4 学习收获
    • 12. 参考资料

1. 题目描述

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

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

示例 1:

输入: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

示例 2:

输入: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 = 14
输出:true

示例 3:

输入: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 = 20
输出:false

提示:

  • m == matrix.length
  • n == matrix[i].length
  • 1 <= n, m <= 300
  • -10^9 <= matrix[i][j] <= 10^9
  • 每行的所有元素从左到右升序排列
  • 每列的所有元素从上到下升序排列
  • -10^9 <= target <= 10^9

2. 理解题目

这道题的关键在于理解矩阵的特殊性质:

2.1 矩阵特性分析

给定的矩阵有两个重要特性:

  1. 行有序:每一行从左到右递增
  2. 列有序:每一列从上到下递增

让我们看一个具体的矩阵例子:

[[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]
]

观察这个矩阵:

  • 第一行:1 < 4 < 7 < 11 < 15
  • 第一列:1 < 2 < 3 < 10 < 18
  • 每个位置的值都比其左边和上边的值大

2.2 关键观察

这种特殊的排列方式给我们提供了重要的搜索线索:

  1. 右上角特性:矩阵右上角的元素(如15)是该行最大值,该列最小值
  2. 左下角特性:矩阵左下角的元素(如18)是该行最小值,该列最大值
  3. 搜索方向:从特殊位置开始,可以根据比较结果确定搜索方向

3. 解法一:从右上角开始搜索(推荐)

3.1 思路分析

从矩阵的右上角开始搜索是最优雅和高效的解法。

核心思想

  • 从右上角位置 (0, n-1) 开始
  • 如果当前值等于目标值,返回 true
  • 如果当前值大于目标值,向左移动(排除当前列)
  • 如果当前值小于目标值,向下移动(排除当前行)

为什么从右上角开始?

  • 右上角元素是该行的最大值,该列的最小值
  • 这个特性让我们能够明确地决定搜索方向
  • 每次比较都能排除一行或一列,确保搜索效率

3.2 图解演示

让我们用示例矩阵搜索目标值 14:

初始状态:从右上角(0,4)开始,当前值=15
[[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]
]
15 > 14,向左移动步骤1:移动到(0,3),当前值=11
[[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]
]
11 < 14,向下移动步骤2:移动到(1,3),当前值=12
[[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]
]
12 < 14,向下移动步骤3:移动到(2,3),当前值=16
[[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]
]
16 > 14,向左移动步骤4:移动到(2,2),当前值=9
[[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]
]
9 < 14,向下移动步骤5:移动到(3,2),当前值=14
[[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]
]
14 == 14,找到目标!返回true

3.3 Java代码实现

class Solution {public boolean searchMatrix(int[][] matrix, int target) {// 处理边界情况if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {return false;}int m = matrix.length;    // 行数int n = matrix[0].length; // 列数// 从右上角开始搜索int row = 0;         // 起始行:第一行int col = n - 1;     // 起始列:最后一列// 当行和列都在有效范围内时继续搜索while (row < m && col >= 0) {int current = matrix[row][col];if (current == target) {// 找到目标值return true;} else if (current > target) {// 当前值大于目标值,向左移动(排除当前列)col--;} else {// 当前值小于目标值,向下移动(排除当前行)row++;}}// 搜索完毕,未找到目标值return false;}
}

3.4 代码详解

让我们逐行分析代码的每个部分:

// 处理边界情况
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {return false;
}
  • 检查矩阵是否为空或者没有元素
  • 这是防御性编程的重要实践
int m = matrix.length;    // 行数
int n = matrix[0].length; // 列数
  • 获取矩阵的维度信息
  • m 表示行数,n 表示列数
// 从右上角开始搜索
int row = 0;         // 起始行:第一行
int col = n - 1;     // 起始列:最后一列
  • 初始化搜索起点为右上角
  • row = 0:第一行
  • col = n - 1:最后一列
while (row < m && col >= 0) {int current = matrix[row][col];if (current == target) {return true;} else if (current > target) {col--;  // 向左移动} else {row++;  // 向下移动}
}
  • 循环条件:确保行和列索引都在有效范围内
  • 获取当前位置的值
  • 根据比较结果决定移动方向:
    • 相等:找到目标,返回 true
    • 大于:向左移动(col–)
    • 小于:向下移动(row++)

3.5 复杂度分析

  • 时间复杂度: O(m + n),其中 m 是行数,n 是列数

    • 最坏情况下,我们从右上角走到左下角
    • 每次移动都会排除一行或一列
    • 总移动次数不超过 m + n - 1
  • 空间复杂度: O(1)

    • 只使用了常数个额外变量
    • 不需要额外的数据结构

3.6 适用场景

这种解法是最推荐的,因为:

  1. 代码简洁易懂
  2. 时间复杂度最优
  3. 空间复杂度最优
  4. 逻辑清晰,不容易出错

4. 解法二:从左下角开始搜索

4.1 思路分析

从左下角开始搜索的思路与从右上角开始类似,只是方向相反。

核心思想

  • 从左下角位置 (m-1, 0) 开始
  • 如果当前值等于目标值,返回 true
  • 如果当前值大于目标值,向上移动(排除当前行)
  • 如果当前值小于目标值,向右移动(排除当前列)

4.2 Java代码实现

class Solution {public boolean searchMatrix(int[][] matrix, int target) {// 处理边界情况if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {return false;}int m = matrix.length;    // 行数int n = matrix[0].length; // 列数// 从左下角开始搜索int row = m - 1;     // 起始行:最后一行int col = 0;         // 起始列:第一列// 当行和列都在有效范围内时继续搜索while (row >= 0 && col < n) {int current = matrix[row][col];if (current == target) {// 找到目标值return true;} else if (current > target) {// 当前值大于目标值,向上移动(排除当前行)row--;} else {// 当前值小于目标值,向右移动(排除当前列)col++;}}// 搜索完毕,未找到目标值return false;}
}

4.3 图解演示

让我们用示例矩阵从左下角搜索目标值 14:

初始状态:从左下角(4,0)开始,当前值=18
[[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] ← 当前位置
]
18 > 14,向上移动步骤1:移动到(3,0),当前值=10
[[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]
]
10 < 14,向右移动步骤2:移动到(3,1),当前值=13
[[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]
]
13 < 14,向右移动步骤3:移动到(3,2),当前值=14
[[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]
]
14 == 14,找到目标!返回true

4.4 复杂度分析

  • 时间复杂度: O(m + n),与解法一相同
  • 空间复杂度: O(1),与解法一相同

5. 解法三:逐行二分搜索

5.1 思路分析

由于每一行都是有序的,我们可以对每一行进行二分搜索。

核心思想

  • 遍历矩阵的每一行
  • 对每一行使用二分搜索查找目标值
  • 如果在某一行找到目标值,返回 true
  • 如果所有行都搜索完毕仍未找到,返回 false

5.2 Java代码实现

class Solution {public boolean searchMatrix(int[][] matrix, int target) {// 处理边界情况if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {return false;}int m = matrix.length;    // 行数int n = matrix[0].length; // 列数// 对每一行进行二分搜索for (int i = 0; i < m; i++) {if (binarySearch(matrix[i], target)) {return true;}}return false;}/*** 在有序数组中进行二分搜索* @param row 有序数组(矩阵的一行)* @param target 目标值* @return 是否找到目标值*/private boolean binarySearch(int[] row, int target) {int left = 0;int right = row.length - 1;while (left <= right) {int mid = left + (right - left) / 2;if (row[mid] == target) {return true;} else if (row[mid] < target) {left = mid + 1;} else {right = mid - 1;}}return false;}
}

5.3 代码详解

// 对每一行进行二分搜索
for (int i = 0; i < m; i++) {if (binarySearch(matrix[i], target)) {return true;}
}
  • 遍历矩阵的每一行
  • 对每一行调用二分搜索函数
  • 如果在某一行找到目标值,立即返回 true
private boolean binarySearch(int[] row, int target) {int left = 0;int right = row.length - 1;while (left <= right) {int mid = left + (right - left) / 2;if (row[mid] == target) {return true;} else if (row[mid] < target) {left = mid + 1;} else {right = mid - 1;}}return false;
}
  • 标准的二分搜索实现
  • 在有序数组中查找目标值
  • 时间复杂度 O(log n)

5.4 复杂度分析

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

    • 需要对 m 行进行搜索
    • 每行的二分搜索时间复杂度为 O(log n)
    • 总时间复杂度为 O(m log n)
  • 空间复杂度: O(1)

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

5.5 优化版本:智能剪枝

我们可以对逐行二分搜索进行优化,利用矩阵的列有序特性进行剪枝:

class Solution {public boolean searchMatrix(int[][] matrix, int target) {if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {return false;}int m = matrix.length;int n = matrix[0].length;for (int i = 0; i < m; i++) {// 剪枝:如果当前行的第一个元素大于目标值,后续行也不可能包含目标值if (matrix[i][0] > target) {break;}// 剪枝:如果当前行的最后一个元素小于目标值,跳过当前行if (matrix[i][n - 1] < target) {continue;}// 在当前行进行二分搜索if (binarySearch(matrix[i], target)) {return true;}}return false;}private boolean binarySearch(int[] row, int target) {int left = 0;int right = row.length - 1;while (left <= right) {int mid = left + (right - left) / 2;if (row[mid] == target) {return true;} else if (row[mid] < target) {left = mid + 1;} else {right = mid - 1;}}return false;}
}

优化说明

  1. 提前终止:如果某行的第一个元素大于目标值,由于列有序,后续行的第一个元素也会更大,可以提前终止
  2. 跳过无效行:如果某行的最后一个元素小于目标值,该行不可能包含目标值,直接跳过

6. 解法四:分治法

6.1 思路分析

分治法利用矩阵的有序特性,将搜索空间递归地分割成更小的子问题。

核心思想

  • 选择矩阵的中心点作为分割点
  • 根据中心点的值与目标值的比较结果,排除不可能包含目标值的区域
  • 递归搜索剩余的区域

6.2 Java代码实现

class Solution {public boolean searchMatrix(int[][] matrix, int target) {if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {return false;}return searchHelper(matrix, target, 0, 0, matrix.length - 1, matrix[0].length - 1);}/*** 分治搜索辅助函数* @param matrix 矩阵* @param target 目标值* @param row1 搜索区域的起始行* @param col1 搜索区域的起始列* @param row2 搜索区域的结束行* @param col2 搜索区域的结束列* @return 是否找到目标值*/private boolean searchHelper(int[][] matrix, int target, int row1, int col1, int row2, int col2) {// 边界条件:搜索区域无效if (row1 > row2 || col1 > col2) {return false;}// 边界条件:搜索区域只有一个元素if (row1 == row2 && col1 == col2) {return matrix[row1][col1] == target;}// 选择中心点int midRow = row1 + (row2 - row1) / 2;int midCol = col1 + (col2 - col1) / 2;int midValue = matrix[midRow][midCol];if (midValue == target) {return true;} else if (midValue > target) {// 目标值在左上区域,搜索三个子区域return searchHelper(matrix, target, row1, col1, midRow - 1, col2) ||  // 上半部分searchHelper(matrix, target, midRow, col1, row2, midCol - 1);   // 左下部分} else {// 目标值在右下区域,搜索三个子区域return searchHelper(matrix, target, row1, midCol + 1, midRow, col2) ||  // 右上部分searchHelper(matrix, target, midRow + 1, col1, row2, col2);      // 下半部分}}
}

6.3 复杂度分析

  • 时间复杂度: O(n^1.58),其中 n 是矩阵的较大维度

    • 每次递归将问题规模减少约 3/4
    • 递归深度约为 log n
    • 总时间复杂度介于 O(n) 和 O(n^2) 之间
  • 空间复杂度: O(log n)

    • 递归调用栈的深度为 O(log n)

7. 详细步骤分析与示例跟踪

让我们通过几个具体例子来详细跟踪不同算法的执行过程。

7.1 示例 1:目标值存在的情况

输入

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. 初始化:row = 0, col = 4, current = 15
  2. 步骤1:15 > 5,向左移动,col = 3, current = 11
  3. 步骤2:11 > 5,向左移动,col = 2, current = 7
  4. 步骤3:7 > 5,向左移动,col = 1, current = 4
  5. 步骤4:4 < 5,向下移动,row = 1, current = 5
  6. 步骤5:5 == 5,找到目标!返回 true

搜索路径:(0,4) → (0,3) → (0,2) → (0,1) → (1,1)
总步数:5步

7.2 示例 2:目标值不存在的情况

输入

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 = 20

使用解法一(从右上角搜索)

  1. 初始化:row = 0, col = 4, current = 15
  2. 步骤1:15 < 20,向下移动,row = 1, current = 19
  3. 步骤2:19 < 20,向下移动,row = 2, current = 22
  4. 步骤3:22 > 20,向左移动,col = 3, current = 16
  5. 步骤4:16 < 20,向下移动,row = 3, current = 17
  6. 步骤5:17 < 20,向下移动,row = 4, current = 24
  7. 步骤6:24 > 20,向左移动,col = 2, current = 23
  8. 步骤7:23 > 20,向左移动,col = 1, current = 21
  9. 步骤8:21 > 20,向左移动,col = 0, current = 18
  10. 步骤9:18 < 20,向下移动,row = 5,超出边界,搜索结束

搜索路径:(0,4) → (1,4) → (2,4) → (2,3) → (3,3) → (4,3) → (4,2) → (4,1) → (4,0) → 越界
结果:false

7.3 边界情况分析

情况1:目标值小于矩阵最小值

matrix = [[1, 2], [3, 4]]
target = 0

从右上角开始:(0,1) → (0,0) → 越界,返回 false

情况2:目标值大于矩阵最大值

matrix = [[1, 2], [3, 4]]
target = 5

从右上角开始:(0,1) → (1,1) → 越界,返回 false

情况3:单元素矩阵

matrix = [[1]]
target = 1

从右上角开始:(0,0),1 == 1,返回 true

8. 常见错误与优化

8.1 常见错误

  1. 边界条件处理不当

    // 错误:没有检查矩阵是否为空
    public boolean searchMatrix(int[][] matrix, int target) {int row = 0;int col = matrix[0].length - 1; // 可能抛出空指针异常// ...
    }// 正确:先检查边界条件
    public boolean searchMatrix(int[][] matrix, int target) {if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {return false;}// ...
    }
    
  2. 搜索方向错误

    // 错误:从右上角开始但移动方向错误
    if (current > target) {row++; // 应该是col--
    } else {col--; // 应该是row++
    }// 正确:从右上角开始的正确移动方向
    if (current > target) {col--; // 向左移动
    } else {row++; // 向下移动
    }
    
  3. 循环条件错误

    // 错误:循环条件不正确
    while (row < m || col >= 0) { // 应该是&&而不是||// ...
    }// 正确:两个条件都必须满足
    while (row < m && col >= 0) {// ...
    }
    
  4. 数组越界

    // 错误:没有检查索引范围
    while (true) {int current = matrix[row][col]; // 可能越界// ...
    }// 正确:在循环条件中检查范围
    while (row < m && col >= 0) {int current = matrix[row][col];// ...
    }
    

8.2 性能优化

  1. 提前终止优化

    public boolean searchMatrix(int[][] matrix, int target) {if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {return false;}// 优化:检查目标值是否在矩阵范围内if (target < matrix[0][0] || target > matrix[matrix.length - 1][matrix[0].length - 1]) {return false;}// 从右上角开始搜索int row = 0;         // 起始行int col = matrix[0].length - 1;     // 起始列while (row < matrix.length && col >= 0) {int current = matrix[row][col];if (current == target) {return true;} else if (current > target) {// 当前值大于目标值,向左移动col--;} else {// 当前值小于目标值,向下移动row++;}}return false;
    }
    
  2. 缓存矩阵维度

    // 优化:缓存矩阵维度,避免重复计算
    int m = matrix.length;
    int n = matrix[0].length;int row = 0;
    int col = n - 1; // 使用缓存的nwhile (row < m && col >= 0) { // 使用缓存的m// ...
    }
    
  3. 减少数组访问

    // 优化:减少重复的数组访问
    while (row < m && col >= 0) {int current = matrix[row][col]; // 只访问一次if (current == target) {return true;} else if (current > target) {col--;} else {row++;}
    }
    

9. 扩展题目与应用

9.1 相关LeetCode题目

  1. LeetCode 74. 搜索二维矩阵

    • 矩阵每行有序,且每行第一个元素大于前一行最后一个元素
    • 可以将矩阵看作一维有序数组进行二分搜索
  2. LeetCode 378. 有序矩阵中第K小的元素

    • 在行列都有序的矩阵中找第K小的元素
    • 可以使用二分搜索或堆的方法
  3. LeetCode 1351. 统计有序矩阵中的负数

    • 统计行列都有序的矩阵中负数的个数
    • 可以使用类似的从角落开始搜索的方法

9.2 实际应用场景

  1. 数据库索引查询

    • 多维索引结构中的范围查询
    • 利用索引的有序性快速定位数据
  2. 图像处理

    • 在有序的像素矩阵中查找特定值
    • 图像分割和特征检测
  3. 游戏开发

    • 在有序的地图网格中查找特定位置
    • 路径规划和碰撞检测
  4. 科学计算

    • 在有序的数据矩阵中查找特定数值
    • 数值分析和统计计算

10. 完整的Java解决方案

以下是结合了各种优化和最佳实践的完整解决方案:

/*** LeetCode 240. 搜索二维矩阵 II* * 解法:从右上角开始搜索* 时间复杂度:O(m + n)* 空间复杂度:O(1)*/
class Solution {public boolean searchMatrix(int[][] matrix, int target) {// 边界条件检查if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {return false;}int m = matrix.length;    // 行数int n = matrix[0].length; // 列数// 快速排除:检查目标值是否在矩阵范围内if (target < matrix[0][0] || target > matrix[m - 1][n - 1]) {return false;}// 从右上角开始搜索int row = 0;         // 起始行int col = n - 1;     // 起始列while (row < m && col >= 0) {int current = matrix[row][col];if (current == target) {return true;} else if (current > target) {// 当前值大于目标值,向左移动col--;} else {// 当前值小于目标值,向下移动row++;}}return false;}
}/*** 测试类*/
public class SearchMatrix2DTest {public static void main(String[] args) {Solution solution = new Solution();// 测试用例1:目标值存在int[][] matrix1 = {{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}};System.out.println("测试用例1:");System.out.println("查找5:" + solution.searchMatrix(matrix1, 5));   // trueSystem.out.println("查找14:" + solution.searchMatrix(matrix1, 14)); // trueSystem.out.println("查找20:" + solution.searchMatrix(matrix1, 20)); // false// 测试用例2:边界情况int[][] matrix2 = {{1}};System.out.println("\n测试用例2:");System.out.println("单元素矩阵查找1:" + solution.searchMatrix(matrix2, 1)); // trueSystem.out.println("单元素矩阵查找2:" + solution.searchMatrix(matrix2, 2)); // false// 测试用例3:空矩阵int[][] matrix3 = {};System.out.println("\n测试用例3:");System.out.println("空矩阵查找1:" + solution.searchMatrix(matrix3, 1)); // false}
}

10.1 多种解法的性能对比

/*** 性能对比测试类*/
public class PerformanceComparison {// 解法1:从右上角搜索public boolean searchFromTopRight(int[][] matrix, int target) {if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {return false;}int row = 0, 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;}// 解法2:从左下角搜索public boolean searchFromBottomLeft(int[][] matrix, int target) {if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {return false;}int row = matrix.length - 1, col = 0;while (row >= 0 && col < matrix[0].length) {if (matrix[row][col] == target) return true;else if (matrix[row][col] > target) row--;else col++;}return false;}// 解法3:逐行二分搜索public boolean searchWithBinarySearch(int[][] matrix, int target) {if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {return false;}for (int[] row : matrix) {if (binarySearch(row, target)) {return true;}}return false;}private boolean binarySearch(int[] row, int target) {int left = 0, right = row.length - 1;while (left <= right) {int mid = left + (right - left) / 2;if (row[mid] == target) return true;else if (row[mid] < target) left = mid + 1;else right = mid - 1;}return false;}public static void main(String[] args) {PerformanceComparison pc = new PerformanceComparison();// 创建测试矩阵int[][] matrix = generateMatrix(1000, 1000);int target = 500000;// 测试各种解法的性能long startTime, endTime;// 解法1startTime = System.nanoTime();boolean result1 = pc.searchFromTopRight(matrix, target);endTime = System.nanoTime();System.out.println("从右上角搜索:" + result1 + ",耗时:" + (endTime - startTime) + "ns");// 解法2startTime = System.nanoTime();boolean result2 = pc.searchFromBottomLeft(matrix, target);endTime = System.nanoTime();System.out.println("从左下角搜索:" + result2 + ",耗时:" + (endTime - startTime) + "ns");// 解法3startTime = System.nanoTime();boolean result3 = pc.searchWithBinarySearch(matrix, target);endTime = System.nanoTime();System.out.println("逐行二分搜索:" + result3 + ",耗时:" + (endTime - startTime) + "ns");}// 生成测试矩阵private static int[][] generateMatrix(int m, int n) {int[][] matrix = new int[m][n];int value = 1;for (int i = 0; i < m; i++) {for (int j = 0; j < n; j++) {matrix[i][j] = value++;}}return matrix;}
}

11. 总结与技巧

11.1 解题要点

  1. 理解矩阵特性:行有序 + 列有序是解题的关键
  2. 选择合适起点:右上角或左下角都是很好的起点
  3. 明确搜索方向:根据比较结果确定唯一的移动方向
  4. 处理边界条件:空矩阵、单元素矩阵等特殊情况

11.2 算法选择建议

  • 推荐解法:从右上角开始搜索(解法一)

    • 代码简洁,逻辑清晰
    • 时间复杂度最优:O(m + n)
    • 空间复杂度最优:O(1)
  • 备选解法:从左下角开始搜索(解法二)

    • 与解法一等价,只是方向不同
    • 复杂度相同
  • 特殊情况:逐行二分搜索(解法三)

    • 当矩阵行数远小于列数时可能更优
    • 时间复杂度:O(m log n)

11.3 面试技巧

如果在面试中遇到此类问题:

  1. 分析题目

    • 明确矩阵的有序特性
    • 理解搜索目标
  2. 提出思路

    • 从暴力搜索开始分析
    • 逐步优化到最优解法
  3. 编写代码

    • 先处理边界条件
    • 实现核心搜索逻辑
    • 注意循环条件和移动方向
  4. 测试验证

    • 用示例数据验证
    • 考虑边界情况
  5. 复杂度分析

    • 分析时间和空间复杂度
    • 讨论可能的优化

11.4 学习收获

通过学习这道题,你可以掌握:

  • 二维矩阵搜索的经典技巧
  • 利用数据结构特性优化算法
  • 从角落开始搜索的思想
  • 分治和二分搜索的应用

这些技巧在解决其他矩阵相关问题时都非常有用。

12. 参考资料

  • LeetCode 官方题解:搜索二维矩阵 II
  • LeetCode 题目链接:搜索二维矩阵 II

相关文章:

  • 【计算机网络】基于TCP进行socket编程——实现客户端到服务端远程命令行操作
  • DAY36
  • 【MySQL】第十一弹——JDBC编程
  • PHP学习笔记(十)
  • 特征预处理
  • QML混合编程图像刷新策略
  • JavaSE核心知识点03高级特性
  • JavaScript数据类型及内置函数详解目录
  • 【MYSQL】索引、存储引擎
  • Next.js V15 在异步页面中,获取路由动态参数的几个坑
  • 【二刷力扣】【力扣热题100】今天的题目是:283.移动零
  • 2025.5.22 Axure 基础与线框图制作学习笔记
  • 大数据Spark(六十):Spark On Yarn 配置
  • 软件工程重点复习
  • C++学习之打车软件—高德地图API
  • 图解 | 大模型智能体LLM Agents
  • 网络原理入门详解:从零理解互联网如何工作
  • mysql知识点1--了解数据库
  • c/c++的opencv椒盐噪声
  • 学习日志12 java
  • 手机怎么建立网站/网站发布与推广
  • java做网站与php做网站/专业搜索引擎seo公司
  • 宠物网站建设论文/2023搜索最多的关键词
  • 门户网站建设投标书/网站后台管理系统
  • 婚庆公司网站建设总结/二级域名查询入口
  • 财经门户网站开发/国内好的seo