文章目录
-
- [1. 题目描述](#1. 题目描述)
- [2. 理解题目](#2. 理解题目)
-
- [2.1 矩阵特性分析](#2.1 矩阵特性分析)
- [2.2 关键观察](#2.2 关键观察)
- [3. 解法一:从右上角开始搜索(推荐)](#3. 解法一:从右上角开始搜索(推荐))
-
- [3.1 思路分析](#3.1 思路分析)
- [3.2 图解演示](#3.2 图解演示)
- [3.3 Java代码实现](#3.3 Java代码实现)
- [3.4 代码详解](#3.4 代码详解)
- [3.5 复杂度分析](#3.5 复杂度分析)
- [3.6 适用场景](#3.6 适用场景)
- [4. 解法二:从左下角开始搜索](#4. 解法二:从左下角开始搜索)
-
- [4.1 思路分析](#4.1 思路分析)
- [4.2 Java代码实现](#4.2 Java代码实现)
- [4.3 图解演示](#4.3 图解演示)
- [4.4 复杂度分析](#4.4 复杂度分析)
- [5. 解法三:逐行二分搜索](#5. 解法三:逐行二分搜索)
-
- [5.1 思路分析](#5.1 思路分析)
- [5.2 Java代码实现](#5.2 Java代码实现)
- [5.3 代码详解](#5.3 代码详解)
- [5.4 复杂度分析](#5.4 复杂度分析)
- [5.5 优化版本:智能剪枝](#5.5 优化版本:智能剪枝)
- [6. 解法四:分治法](#6. 解法四:分治法)
-
- [6.1 思路分析](#6.1 思路分析)
- [6.2 Java代码实现](#6.2 Java代码实现)
- [6.3 复杂度分析](#6.3 复杂度分析)
- [7. 详细步骤分析与示例跟踪](#7. 详细步骤分析与示例跟踪)
-
- [7.1 示例 1:目标值存在的情况](#7.1 示例 1:目标值存在的情况)
- [7.2 示例 2:目标值不存在的情况](#7.2 示例 2:目标值不存在的情况)
- [7.3 边界情况分析](#7.3 边界情况分析)
- [8. 常见错误与优化](#8. 常见错误与优化)
-
- [8.1 常见错误](#8.1 常见错误)
- [8.2 性能优化](#8.2 性能优化)
- [9. 扩展题目与应用](#9. 扩展题目与应用)
-
- [9.1 相关LeetCode题目](#9.1 相关LeetCode题目)
- [9.2 实际应用场景](#9.2 实际应用场景)
- [10. 完整的Java解决方案](#10. 完整的Java解决方案)
-
- [10.1 多种解法的性能对比](#10.1 多种解法的性能对比)
- [11. 总结与技巧](#11. 总结与技巧)
-
- [11.1 解题要点](#11.1 解题要点)
- [11.2 算法选择建议](#11.2 算法选择建议)
- [11.3 面试技巧](#11.3 面试技巧)
- [11.4 学习收获](#11.4 学习收获)
- [12. 参考资料](#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代码实现
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 代码详解
让我们逐行分析代码的每个部分:
java
// 处理边界情况
if (matrix == null || matrix.length == 0 || matrix[0].length == 0) {
return false;
}
- 检查矩阵是否为空或者没有元素
- 这是防御性编程的重要实践
java
int m = matrix.length; // 行数
int n = matrix[0].length; // 列数
- 获取矩阵的维度信息
- m 表示行数,n 表示列数
java
// 从右上角开始搜索
int row = 0; // 起始行:第一行
int col = n - 1; // 起始列:最后一列
- 初始化搜索起点为右上角
- row = 0:第一行
- col = n - 1:最后一列
java
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代码实现
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代码实现
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 代码详解
java
// 对每一行进行二分搜索
for (int i = 0; i < m; i++) {
if (binarySearch(matrix[i], target)) {
return true;
}
}
- 遍历矩阵的每一行
- 对每一行调用二分搜索函数
- 如果在某一行找到目标值,立即返回 true
java
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 优化版本:智能剪枝
我们可以对逐行二分搜索进行优化,利用矩阵的列有序特性进行剪枝:
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 (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代码实现
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 常见错误
-
边界条件处理不当:
java// 错误:没有检查矩阵是否为空 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; } // ... }
-
搜索方向错误:
java// 错误:从右上角开始但移动方向错误 if (current > target) { row++; // 应该是col-- } else { col--; // 应该是row++ } // 正确:从右上角开始的正确移动方向 if (current > target) { col--; // 向左移动 } else { row++; // 向下移动 }
-
循环条件错误:
java// 错误:循环条件不正确 while (row < m || col >= 0) { // 应该是&&而不是|| // ... } // 正确:两个条件都必须满足 while (row < m && col >= 0) { // ... }
-
数组越界:
java// 错误:没有检查索引范围 while (true) { int current = matrix[row][col]; // 可能越界 // ... } // 正确:在循环条件中检查范围 while (row < m && col >= 0) { int current = matrix[row][col]; // ... }
8.2 性能优化
-
提前终止优化:
javapublic 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; }
-
缓存矩阵维度:
java// 优化:缓存矩阵维度,避免重复计算 int m = matrix.length; int n = matrix[0].length; int row = 0; int col = n - 1; // 使用缓存的n while (row < m && col >= 0) { // 使用缓存的m // ... }
-
减少数组访问:
java// 优化:减少重复的数组访问 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解决方案
以下是结合了各种优化和最佳实践的完整解决方案:
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)); // true
System.out.println("查找14:" + solution.searchMatrix(matrix1, 14)); // true
System.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)); // true
System.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 多种解法的性能对比
java
/**
* 性能对比测试类
*/
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;
// 解法1
startTime = System.nanoTime();
boolean result1 = pc.searchFromTopRight(matrix, target);
endTime = System.nanoTime();
System.out.println("从右上角搜索:" + result1 + ",耗时:" + (endTime - startTime) + "ns");
// 解法2
startTime = System.nanoTime();
boolean result2 = pc.searchFromBottomLeft(matrix, target);
endTime = System.nanoTime();
System.out.println("从左下角搜索:" + result2 + ",耗时:" + (endTime - startTime) + "ns");
// 解法3
startTime = 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 学习收获
通过学习这道题,你可以掌握:
- 二维矩阵搜索的经典技巧
- 利用数据结构特性优化算法
- 从角落开始搜索的思想
- 分治和二分搜索的应用
这些技巧在解决其他矩阵相关问题时都非常有用。