在一个 n * m 的二维数组中,每一行都按照从左到右 非递减 的顺序排序,每一列都按照从上到下 非递减 的顺序排序。请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
示例:
现有矩阵 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
。
给定 target = 20
,返回 false
。
思路分析
首先每一行从左至右和每一行从上至下都不是递减的,想要在这个二维数组中判断目标值是否存在,暴力法是行的通的,但是这样做时间复杂度就会达到O(nm),看到题目中的条件,我最先能想到的方法就是使用二分查找法,对每一行都进行二分查找,判断target是否在此行中,但是这种方法并没有使用到每一列从上至下也是非递减的方式,因此这种方法必然也不是最优解,这里我先给出我的二分查找法进行求解,最后在说说我对最优解的理解。
二分查找法
class Solution {
public boolean findNumberIn2DArray(int[][] matrix, int target) {
for(int[] row : matrix){
if(search(row,target)){ //调用search方法进行查找
return true; //if判断为true说明查找到了目标值,直接返回
}
}
return false;
}
private boolean search(int[] row,int target){ //二分查找法
int l = 0; //左指针
int r = row.length - 1; //右指针
while(l <= r){ //结束条件
int m = (l + r) / 2; //中间位置
if(row[m] == target){
return true;
}else if(row[m] < target){
l = m + 1;
}else{
r = m - 1;
}
}
return false;
}
}
二分查找法的时间复杂度相对于暴力解法使用提升的,但这还并不是这道题的最优解,看了大神的题解以后顿时觉得自己和大神的差距是相隔万里的,还是好好学学大神的思路吧。
大神解法
将矩阵逆时针旋转 45° ,并将其转化为图形式,发现其类似于 二叉搜索树 ,即对于每个元素,其左分支元素更小、右分支元素更大。因此,通过从 “根节点” 开始搜索,遇到比 target 大的元素就向根节点左方搜索,反之向根节点右方搜索,即可找到目标值 target 。
我们假设右上角的元素为标志数flag--即根节点,若flag > target,那么我们就应该向标志数的左方进行查找,那么标志数所在的列就可以被舍弃,反之flag < target,我们就应该向标志数的右方进行查找,那么标志数所在的行就可以被舍弃。代码如下:
class Solution {
public boolean findNumberIn2DArray(int[][] matrix, int target) {
if(matrix.length == 0){ //如果二维数组为空,直接返回false
return false;
}
int i = 0; //标志数所在行
int j = matrix[0].length - 1; //标志数所在列
while(j >= 0 && i < matrix.length){
if(matrix[i][j] > target){ //目标值小于标志数
j--; //舍弃标志数所在列
}else if (matrix[i][j] < target){ //目标值大于标志数
i++; //舍弃标志数所在行
}else{
return true;
}
}
return false;
}
}
- 时间复杂度 O(M+N)O(M+N)O(M+N) :其中,NNN 和 MMM 分别为矩阵行数和列数,此算法最多循环 M+NM+NM+N 次。
- 空间复杂度 O(1) :
i
,j
指针使用常数大小额外空间。
这个解法最关键的点在与二维数组的行与列均是非递减的形式,联想到旋转二维数组很好的将题目中所给的条件应用到,从而降低时间复杂度。