题目描述
在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
解决思路
对于一个二维数组而言,我们可以很轻松地想到,我们用暴力解法的O(N2),遍历这个二维数组就可以找这个元素。
//运行时间:13ms
//运行内存:1358K
class Solution {
public:
bool Find(int target, vector<vector<int> > array) {
int rows = array.size();
int columns = array[0].size();
for(int i=0;i<rows;i++)
for(int j=0;j<columns;j++)
if(array[i][j]==target)
return true;
return false;
}
};
但是,很显然,这不是最佳的解法,我们没有充分利用每一行,每一列都是单调递增顺序,所以这道题的关键在于如何活用这个条件。
思路一:二分法查找
这里我们很快会想到,一种活用单调递增顺序的元素查找方法–二分查找法。
//运行时间:16ms
//运行内存:1492K
class Solution {
public:
bool Find(int target, vector<vector<int> > array) {
int rows = array.size();
int columns = array[0].size();
for(int i=0;i<rows;i++)
{ int left=0;
int right=columns-1;
while(left<=right)
{
int mid=left+(right-left)/2;
if(array[i][mid]==target)
return true;
if(array[i][mid]<target)
left = mid+1;
else right = mid - 1;
}
}
return false;
}
};
理论上来说
结果发现运行起来比暴力解法还花费时间,我估计是因为测试用例的二维矩阵比较小导致的。因为理论上来说这是一个O(nlogn)复杂度的算法。
但这也不是最优的思路。
思路二:通过归纳总结数组查找规律
我们可以实际的自己画一个矩阵出来,会发现如下的查找规律:
-
首先选取二维数组当中右上角的数字。
- 1.如果该数字等于target,则查找过程结束。
- 2.如果该数字大于target,则剔除这个数字的所在列(由于该列是递增顺序,这个数字是这一列里的最小的数字,这一列的数字都会比target大)
- 3.如果该数字小于target,则剔除这个数字的所在行(同理,这个数字是这一行里最大的数字,所以这一行的数字都要比target小)
-
此时再次选取剩余区域的右上角的数字,然后重复上述的过程。
每一次都会缩小范围,直到找到我们的数字,或者查找范围为空。
整理清楚了以后,就不难写出我们的代码
//运行时间:12ms
//占用内存:1372K
class Solution {
public:
bool Find(int target, vector<vector<int> > array) {
int rows = array.size();
int columns = array[0].size();
if(rows>0&&columns>0)
{ int row = 0 ;//代表搜索时,被比较元素的行
int column = columns-1;//代表搜索时,被比较元素的列
while(row<rows&&column>=0)
{
if(array[row][column]==target)
return true;
else if(array[row][column]>target)
column--;
else row++;
}
}
return false;
}
};
显然,这是一个最佳的解决方案,最大限度的减小了我们搜索的范围,只要观察到我们查找范围的这个规律就可以实现了。
小结
解决思路里虽然代码可以提交,但是没有对有序的数组这个条件进行有效的使用。
思路一对于这个有序的数组的条件进行了使用,使用二分查找法,将算法复杂度降低到了O(nlogn)。
思路二最大限度的减小了我们搜索的范围。
整体而言,思路二最佳。
参考文献
《剑指offer》
牛课网刷题链接link.