剑指offer 二位数组中的数字查找

1.题目

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

来源:剑指offer
链接:https://www.nowcoder.com/practice/abc3fe2ce8e146608e868a70efebf62e?tpId=13&tqId=11154&rp=1&ru=/ta/coding-interviews&qru=/ta/coding-interviews/question-ranking

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。

发布了32 篇原创文章 · 获赞 0 · 访问量 429

猜你喜欢

转载自blog.csdn.net/weixin_43951240/article/details/103906051