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, 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 关键观察
这种特殊的排列方式给我们提供了重要的搜索线索:
- 右上角特性:矩阵右上角的元素(如15)是该行最大值,该列最小值
- 左下角特性:矩阵左下角的元素(如18)是该行最小值,该列最大值
- 搜索方向:从特殊位置开始,可以根据比较结果确定搜索方向
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 适用场景
这种解法是最推荐的,因为:
- 代码简洁易懂
- 时间复杂度最优
- 空间复杂度最优
- 逻辑清晰,不容易出错
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;}
}
优化说明:
- 提前终止:如果某行的第一个元素大于目标值,由于列有序,后续行的第一个元素也会更大,可以提前终止
- 跳过无效行:如果某行的最后一个元素小于目标值,该行不可能包含目标值,直接跳过
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
使用解法一(从右上角搜索):
- 初始化:row = 0, col = 4, current = 15
- 步骤1:15 > 5,向左移动,col = 3, current = 11
- 步骤2:11 > 5,向左移动,col = 2, current = 7
- 步骤3:7 > 5,向左移动,col = 1, current = 4
- 步骤4:4 < 5,向下移动,row = 1, current = 5
- 步骤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
使用解法一(从右上角搜索):
- 初始化:row = 0, col = 4, current = 15
- 步骤1:15 < 20,向下移动,row = 1, current = 19
- 步骤2:19 < 20,向下移动,row = 2, current = 22
- 步骤3:22 > 20,向左移动,col = 3, current = 16
- 步骤4:16 < 20,向下移动,row = 3, current = 17
- 步骤5:17 < 20,向下移动,row = 4, current = 24
- 步骤6:24 > 20,向左移动,col = 2, current = 23
- 步骤7:23 > 20,向左移动,col = 1, current = 21
- 步骤8:21 > 20,向左移动,col = 0, current = 18
- 步骤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 常见错误
-
边界条件处理不当:
// 错误:没有检查矩阵是否为空 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;}// ... }
-
搜索方向错误:
// 错误:从右上角开始但移动方向错误 if (current > target) {row++; // 应该是col-- } else {col--; // 应该是row++ }// 正确:从右上角开始的正确移动方向 if (current > target) {col--; // 向左移动 } else {row++; // 向下移动 }
-
循环条件错误:
// 错误:循环条件不正确 while (row < m || col >= 0) { // 应该是&&而不是||// ... }// 正确:两个条件都必须满足 while (row < m && col >= 0) {// ... }
-
数组越界:
// 错误:没有检查索引范围 while (true) {int current = matrix[row][col]; // 可能越界// ... }// 正确:在循环条件中检查范围 while (row < m && col >= 0) {int current = matrix[row][col];// ... }
8.2 性能优化
-
提前终止优化:
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; }
-
缓存矩阵维度:
// 优化:缓存矩阵维度,避免重复计算 int m = matrix.length; int n = matrix[0].length;int row = 0; int col = n - 1; // 使用缓存的nwhile (row < m && col >= 0) { // 使用缓存的m// ... }
-
减少数组访问:
// 优化:减少重复的数组访问 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题目
-
LeetCode 74. 搜索二维矩阵:
- 矩阵每行有序,且每行第一个元素大于前一行最后一个元素
- 可以将矩阵看作一维有序数组进行二分搜索
-
LeetCode 378. 有序矩阵中第K小的元素:
- 在行列都有序的矩阵中找第K小的元素
- 可以使用二分搜索或堆的方法
-
LeetCode 1351. 统计有序矩阵中的负数:
- 统计行列都有序的矩阵中负数的个数
- 可以使用类似的从角落开始搜索的方法
9.2 实际应用场景
-
数据库索引查询:
- 多维索引结构中的范围查询
- 利用索引的有序性快速定位数据
-
图像处理:
- 在有序的像素矩阵中查找特定值
- 图像分割和特征检测
-
游戏开发:
- 在有序的地图网格中查找特定位置
- 路径规划和碰撞检测
-
科学计算:
- 在有序的数据矩阵中查找特定数值
- 数值分析和统计计算
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 解题要点
- 理解矩阵特性:行有序 + 列有序是解题的关键
- 选择合适起点:右上角或左下角都是很好的起点
- 明确搜索方向:根据比较结果确定唯一的移动方向
- 处理边界条件:空矩阵、单元素矩阵等特殊情况
11.2 算法选择建议
-
推荐解法:从右上角开始搜索(解法一)
- 代码简洁,逻辑清晰
- 时间复杂度最优:O(m + n)
- 空间复杂度最优:O(1)
-
备选解法:从左下角开始搜索(解法二)
- 与解法一等价,只是方向不同
- 复杂度相同
-
特殊情况:逐行二分搜索(解法三)
- 当矩阵行数远小于列数时可能更优
- 时间复杂度:O(m log n)
11.3 面试技巧
如果在面试中遇到此类问题:
-
分析题目:
- 明确矩阵的有序特性
- 理解搜索目标
-
提出思路:
- 从暴力搜索开始分析
- 逐步优化到最优解法
-
编写代码:
- 先处理边界条件
- 实现核心搜索逻辑
- 注意循环条件和移动方向
-
测试验证:
- 用示例数据验证
- 考虑边界情况
-
复杂度分析:
- 分析时间和空间复杂度
- 讨论可能的优化
11.4 学习收获
通过学习这道题,你可以掌握:
- 二维矩阵搜索的经典技巧
- 利用数据结构特性优化算法
- 从角落开始搜索的思想
- 分治和二分搜索的应用
这些技巧在解决其他矩阵相关问题时都非常有用。
12. 参考资料
- LeetCode 官方题解:搜索二维矩阵 II
- LeetCode 题目链接:搜索二维矩阵 II