1.题目
题目描述
在一个二维数组中(每个一维数组的长度相同),每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。请完成一个函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
2.我的题解
遍历搜索是可以得到正确答案的,复杂度为O(length*width)
,但显然不是最优解。
关键在于如何利用每一行从左到右递增、每一列从上到下递增的条件。第一眼看(从左上角),右、下两个方向都是增大的,这样就无法进行下一步选择。但如果从右上角看,可以发现一个方向是增大的,一个方向是减小的,这样的情况下选择是确定的,仅有一种选择,复杂度为O(length+width)
。
举个例子,会清晰明了。
(1)以1到8为例。如果从左上角开始,第一步就会面临难题,右和下我该去哪个方向?第一步无论是向右还是向下,都可以得到一条1到8的最优路径。也就是说,在当前值、右方向的值、下方向的值之间也没有足够的关系让我确定该去哪个方向,两个方向都行也就意味着不确定性,这种方法不可行。
(2)以4到9为例。如果从右上角开始,会发现左方向的值小于当前值,下方向的值大于当前值,对比目标值与当前值的关系,当前值大了就向左,当前值小了就向下,这样可以得到一条唯一确定且最优的路径。当然这条路径是最优的,但不是唯一的,它只是在当前方法下唯一的,其他方法仍可以得到另外的最优路径。
(3)为什么不用走回头路?乍一看该方法好像有些道理,但怎么保证不用走回头路就可以到达目的地呢?
这是由于数组从左到右递增、从上到下递增的性质保证的。
可以使用反证法,假设本方法得到的结果存在一条走回头路的路径ABCD,作辅助点F,如下所示,:
那么由本方法的性质,在辅助点F点可以得到:F>D
,所以在F点才回向左走,不向下走;
由本题的条件数据从左向右递增、从上向下递增,可以得到F>D
,F与D的关系出现了矛盾。
因此本方法不会得到这样的有回头路的路径。
3.别人的题解
3.1 十字分割法
将原矩形按中心点分为四个矩形,左上、右下是互斥的,因此可以减少1/4的数据量,在其它三个矩形中递归搜索,直到矩形仅有一个元素。
可以应用剪枝策略减少递归次数。复杂度为O(logn)
class Solution {
public:
bool Find(int target, vector<vector<int> > array) {
if(array.size()==0 || array[0].size()==0)return false;
return ten_seg(array,target,0,array.size()-1,0,array[0].size()-1);
}
bool ten_seg(vector<vector<int> > &data,int target,int top,int bottom,int left,int right){
//递归结束
if(top==bottom && left == right){
if(data[top][left] == target)return true;
return false;
}
//剪枝
if(target>data[bottom][right] || target< data[top][left])return false;
//分割
int i=(top+bottom)/2,j=(left+right)/2;
if(target==data[i][j])return true;
//右下
if((i<bottom && j< right)&&target>data[i][j] && ten_seg(data,target,i+1,bottom,j+1,right))return true;
//左上
else if((top<i || left<j) &&target<data[i][j] && ten_seg(data,target,top,i,left,j)) return true;
//左下
if(i<bottom && ten_seg(data,target,i+1,bottom,left,j))return true;
//右上
if(j<right && ten_seg(data,target,top,i,j+1,right))return true;
return false;
}
};
以上面的数据为例,查找10,递归顺序及输出如下:
# top bottom left right
0,3,0,3
2,3,2,3
2,3,0,1
3,3,1,1
3,3,0,0
2,2,1,1
1
4.总结与反思
(1)找到“确定的操作”,即下一步由某些条件满足然后确定做什么操作,如果下一步存在多个不确定的操作,计算机无法解决问题。
(2)在十字分割法中,尤其要主要边界的处理,要做到不重不漏;在本题中,将中间点分给左上部分,则查找右下部分时行列数要加1,右上部分列数加1,左下部分行数加1。