题目描述
在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
例如下面的二维数组,每行和每列都是递增排序。如果在这个数组中查找数字7,则返回true;如果查找数字5,则返回false
[[1, 2, 8, 9],
[2, 4, 9, 12],
[4, 7, 10, 13],
[6, 8, 11, 15]]
解法一(暴力法):
暴力法不用多做解释了吧,遍历扫描整个二维数组,遍历到要查找的数字则返回true,否则在程序末尾返回false
//暴力解法(遍历所有元素)
public boolean Find1(int target, int [][] array)
{
if(array==null)
{
return false;
}
for(int i=0;i<array.length;i++)
{
for(int j=0;j<array[i].length;j++)
{
if(array[i][j]==target)
{
return true;
}
}
}
return false;
}
时间复杂度:O(n2),空间复杂度:没有用到辅助空间,O(1)
显然这种遍历整个数组的方法不够“高级”,也没有利用到该数组的特性——每行从左到右递增,每列从上到下递增。那么有没有方法可以利用这一特性,使我们不必遍历整个数组呢?
解法二(二分查找+缩小查找范围):
首先,假设给定二维数组array[n1][n2]。我们将查找范围定为整个数组,即以array[0][n]为右上角,以array[n1][0]为左下角的矩形范围。需要注意的是,查找范围的的右上角固定不变,左下角为array[row][inel],即初始状态row=n1,line=0
- 列搜索:在第line列的第0~row数字中进行二分查找。若找到目标数字则返回true,否则:
(1)若目标数字小于第line最小的数字,则可断定数组中没有目标数字,返回false.否则:
(2)更改row为本列小于目标数字的最大数字所在的行,进入第2步 - 行搜索:在第row行的第line~n2数字中二分查找。若找到目标数字则返回true,否则:
(1)若目标数字大于第row行最大的数字,则可断定数组中没有目标数字,返回false,否则:
(2)更改line为本列大于目标数字的最小数字所在的列。到这里row都得到了更新,完成了查找范围的缩小。返回第1步循环
public boolean Find2(int target, int [][] array)
{
if(array==null || array.length==0 || array[0].length==0) //验证空指针或空数组
{
return false;
}
if(array[0][0]>target || array[array.length-1][array[0].length-1]<target) //target大于最大值或小于最小值,返回false
{
return false;
}
int line=0;
int row=array.length-1;
while(true)
{
//在列上搜索,二分查找
int head=0,tail=row;
while(head<=tail)
{
int mid=(head+tail)/2;
if(array[mid][line]==target)
{
return true;
}
else if(array[mid][line]>target)
{
tail=mid-1;
}
else
{
head=mid+1;
}
}
if(tail<0) //目标数字小于本列最小的数字,可断定二维数组中没有目标数字
{
break;
}
//在行上查找(二分查找)
row=tail;
head=line;
tail=array[0].length-1;
while(head<=tail)
{
int mid=(head+tail)/2;
if(array[row][mid]==target)
{
return true;
}
else if(array[row][mid]>target)
{
tail=mid-1;
}
else
{
head=mid+1;
}
}
if(head>(array[0].length-1)) //目标数字大于本行最大的数字,可断定二维数组中没有目标数字
{
break;
}
line=head;
}
return false;
}
时间复杂度:一次行或列上的二分查找时间复杂度为 log(n),每次列查找会伴随一次行查找,行列查找的循环会进行n次,因此为O(nlogn)
空间复杂度:没有用到辅助空间,O(1)
解法二虽然利用了给定二维数组的特性,缩小了查找范围,降低了时间复杂度,但实现和理解起来较为繁琐,尤其是边界条件较为难理解,作者在写代码时感觉很明显。是否还有更简单的方法?
解法三:
解法三是作者参考“剑指offer”提供的官方解法,当看到这个解法时,顿时感觉醍醐灌顶,茅塞顿开
原来可以这样,我怎么想不到……
以上面给出的二维数组为例,查找数字7:每次选取查找范围最左下角的数字,从左下角的数字6开始,6小于7,我们可以确定6所在的列的所有元素均小于7,将这一列剔除出查找范围,比较6右边的8;8大于7,可以确定8所在的行的所有元素均大于7,剔除8所在的行,比较8上面的7;查找到了7
public boolean Find(int target, int [][] array)
{
if(array==null || array.length==0 || array[0].length==0) //验证空指针或空数组
{
return false;
}
if(array[0][0]>target || array[array.length-1][array[0].length-1]<target) //target大于最大值或小于最小值,返回false
{
return false;
}
int row=array.length-1,line=0; //从左下角元素开始比较
while(row>=0 && line<=array[0].length-1)
{
int aim=array[row][line]; //当前关注的元素的值
if(aim==target)
{
return true;
}
else if(aim<target)
{
//当前关注的元素所在的列的值都小于target,line右移
line++;
}
else
{
//当前关注的元素所在的行的值都大于target,row上移
row--;
}
}
return false;
}
时间复杂度:最坏情况下,需要在行和列上交替遍历,共查找n1+n2次,因此为O(n)
空间复杂度:没有用到辅助空间,O(1)