二维数组中的查找(O(mlogn)及O(m+n))

题目描述

在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。

题目分析

根据题目可知,数组的每一行以及每一列都是有序的,对于有序数组的查找,最有效的当然就是二分查找了,二分查找不必多说,不过在这里由于每一行都需要查找一次,那么复杂度就为O(mlogn),
直接给出代码:

bool searchMatrix(vector<vector<int>>& matrix, int target) {
        
        if(matrix.empty())return false;   //空数组返回false
        
        int row=matrix.size();
        int col=matrix[0].size();
        
        if(!col||target>matrix[row-1][col-1]||target<matrix[0][0])return false;   //数组的右下角元素必定是最大的,数组左上角一定是最小的
        
        int right_lim=col-1;
        
        for(int i=0;i<row;i++)   
        {
            int l=0,h=right_lim;
            
            while(l<=h)    //二分查找
            {
                int mid=l+(h-l)/2;
                
                if(matrix[i][mid]<target)l=mid+1;
                else if(matrix[i][mid]>target)
                {
                    h=mid-1;
                    right_lim=mid;
                }
                else return true;  
            }
        }
        return false;
    }
};

上述代码与一般的二分查找模板相比有一些小小的区别,区别在于每一行开始二分时h的初始值一般都是数组的长度,而在这里是通过一个变量right_lim来赋值的,什么意思呢?这是因为这里的二分查找过程是从上往下的,如果目标值比当前值小,由于当前值的上方已经被查找过了,而右下方的值必定比当前值大,那么目标值只可能在当前值的左下方,因此后面的查找根本不用去管mid后面的列了,用right_lim保存mid值,再进行后续行二分查找时,再重新将right_lim值重新赋值给查找右限值即可。
那么这里为什么不能也像右限值一样更新一下左限值呢?原因是因为如果目标值比当前值大,那么目标值既可能在当前值左下方,也可能在当前值的右下方,这是无法确定的,因此是不能更新左限值的。
再来看看更新右限值与不更新的运行时间差别:
不更新值:
在这里插入图片描述
更新值:
在这里插入图片描述

另一种思路:
可以知道,数组中的每个元素都是大于其上方和左方元素,小于其下方和右方元素的,之前之所以二分的左限值无法更新,就是因为无法区分小于当前值的元素是位于左下方还是右下方。那么如果我现在就直接从数组的右上方开始遍历呢?这样就不存在右下方了,也就是只能往左下遍历,那么究竟是往左还是往下呢?
前面也提到了,每个元素的左边元素是小于当前元素的,下方元素又恰好是大于当前元素的,因此从右上角出发,如果当前值小于目标值,那么目标值必定不会在当前值所在行的左边,这样就可以排除当前值所在行,就再遍历当前元素的下方元素;如果当前值大于目标值,那么目标值必定不会在当前值所在列的下边,这样就可以排除当前值所在列,就再遍历当前元素的左方元素,然后新元素再继续比较,再按同样决策进行遍历,可以发现,这样每遍历一个元素必定都能排除一行或一列,如果存在目标值那么就必然能找到,如果最终遍历越界,则代表数组中不存在目标值。可想而知,最坏的情况即是从右上角遍历到左下角,遍历次数为m+n,最好情况即是1次,因此时间复杂度为O(m+n)。

if(matrix.empty())return false;
        
        int row=matrix.size();
        int col=matrix[0].size();
        
        if(!row||!col||target>matrix[row-1][col-1]||target<matrix[0][0])return false;
	
	int find_row=0,find_col=col-1;
	
	while(find_row<row&&find_col>=0)
        {
            if(matrix[find_row][find_col]==target)return true;
            else if(matrix[find_row][find_col]>target)find_col--;
            else find_row++;
        }
        
        return false;

那从左上角、左下角和右下角是否可以呢?显然,对于左上角来说,其右方和下方元素都大于当前值,如果目标值小于当前值,也只能判断出不存在目标值,而如果目标值大于当前值,也无法确定目标值是不在当前行还是不在当前列,因此也就无法确定下一次到底是遍历哪一个元素;右下角与左上角相似,也无法确定;而对于左下角来说,如果目标值大于当前值,那么即可排除当前列,如果目标值小于当前值,那么即可排除当前行,由此来看,从左下角开始遍历也是可以的。

猜你喜欢

转载自blog.csdn.net/qq_28114615/article/details/85225012