题目描述 在一个二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
题目分析:题目表述的二维数组中,每一行是递增,每一列也是递增,首先我们想到的就是暴力搜索,即从二维数组的[0][0]元素开始一直往后遍历,直到发现有一个数与给定的数相等时,就可以结束遍历,返回真,否则返回假。
boolean Find(int [][] array,int target) {
int i,j;
boolean flag=false;
for( i=0;i<array.length;i++)
{
for(j=0;j<array[i].length;j++)
{
if(array[i][j]==target){
flag=true;
break;
}
}
if(flag==true)
break;
}
return flag;
}
但是很明显,这样搜索的时间比较长,有可能需要遍历整个二维数组,没有使用到递增这个条件,那么我们现在要做的就是使用递增这个条件,对算法进行该进:
一种改进方案:我们可以对每行进行二分查找,这样相比顺序查找优化是明显的,这里不给出代码,继续看下面:
我们在从[0][0]元素开始往后遍历的时候,由于横向是递增,那么只要遍历到的数比给定的数大,那么我们可以肯定,当前行后面的数一定比给定的数要大,就不需要继续往后遍历了,还有往下所有行的元素,从当前列开始都不需要继续遍历:
那么又有一种优化方案:在对每行进行二分查找时,查找的结果可以对下一行的二分查找进行限制,如下图第一次对第一行进行二分查找发现8比给定的值大时,那后对第二行进行查找时,红线后面的部分不用查找
如上图中,当我们要在当前二维数组中寻找7时,第一行当遍历到红线的地方时,后面的所有行中,红线右边的都不用遍历,那么此时有两种方式继续遍历,第一种是从第二行的起始位置[1][0]开始继续遍历,到[1][2]结束(或更前),第二种是从紧接着的下方[1][2]开始遍历,且一直往下(增大的方向)开始遍历。很明显从紧接着的地方开始遍历回溯的更少,直到遍历到第一个比给定元素大的值时,开始向左回溯,一直回溯到第一个比给定值小的元素
上图的箭头就是根据上述的思想,在遍历给定数7时所走的路线,最终的下标会落在数组范围外,因此没有找到给定的值。基于这种思想,给出的算法如下:
bool Find(int target, vector<vector<int> > array) {
if(array.empty()){
return false;
}
int i=0;
int j=0;
for(j;j<array[0].size();++j){ //先从第一个行遍历到第一个比给定值大的元素位置
if(array[0][j]>target){
j--;
break;
}
else if(j==array[0].size()-1){ //可能第一行的值都比给定的值小
break;
}
}
while(i<array.size() && j< array[i].size() && j>=0 && target!=array[i][j]){
if(target>array[i][j]){ //第二行开始,比给定值小是向下遍历
i++;
}
else if(target<array[i][j]){ //比给定值小时,向左回溯
j--;
}
else{ //相等时退出
break;
}
}
if(i<array.size() && j< array[i].size()&&j>=0 && target==array[i][j]){
return true;
}
return false;
}
优化的算法使用了数组每行和每列都是递增这个性质,基本上已经接近了最优的算法,但是仔细分析发现,在最开始对第一行进行遍历时,我们可以向右遍历,也可以向下遍历,因为两者都是递增的方向。但是遍历的第一个元素,不一定从[0][0]开始,如果我们选右下角开始遍历,那么两个方向都是递减。但是如果我们从左下角或右上角开始遍历,那么我们遍历的方向就是唯一的了。
如上图我们查找12,如果从左下角开始遍历,我们最开始只能从右边开始遍历,并按照遍历规则,最终没有找到给定的数值,下标落在数组外,返回false。(右上角类似)
算法如下:
bool Find(int target, vector<vector<int> > array) {
if(array.empty()){
return false;
}
int i=array.size()-1; //定位下标到左下角
int j=0;
while(i>=0 && j< array[i].size() && target!=array[i][j]){
if(target>array[i][j]){ //目标数大时,向右遍历
j++;
}
else if(target<array[i][j]){ //目标数小时,向上遍历
i--;
}
else{
break;
}
}
if(i>=0 && j< array[i].size() && target==array[i][j]){
return true;
}
return false;
}
那么前面从左下角开始遍历或者从右上角开始遍历就是最优的了吗,答案是否定的!再仔细看我们的遍历过程,我们依然是一个一个挨着在遍历元素,虽然已经使回溯的次数最少 。前我们想到可以使用二分查找的方法来进行优化,那么二分查找的方法也可以对我们优化的结果继续进行优化。
在从左下角开始遍历时,我们每次都是使用顺序遍历的方式遍历到了第一个比给定值大的元素,接着使用顺序遍历的方式向上找到第一个比给定值小的元素,那么优化的思路,就是把顺序遍历改成二分查找,代码还没写没法贴上。