《剑指Offer》二维数组中的查找

二维数组中的查找

http://www.nowcoder.com/questionTerminal/abc3fe2ce8e146608e868a70efebf62e

分享五种解题方法,仅为拓宽思路:
(注:如果代码出现了段错误问题,可能是没有考虑到空数组,健壮性也是算法的一部分)

一、左下/右上元素移动法:
十分简单巧妙,在看了徘徊的路人甲的题解才后知后觉;
设M=min(m,n),N=max(m,n),复杂度为O(N),详见链接题解:
链接:https://www.nowcoder.com/questionTerminal/abc3fe2ce8e146608e868a70efebf62e?f=discussion

二、双折半查找法:
是我提交时使用的方法,没有法一简洁明了,但是也挺好用,复杂度最坏情况为O(M * logN),不过总体上还是比较优秀的,大概率可以取得O(logM + logN)的效率
二维数组分为上下左右四个边界top,bottom,left,right:
对上边界top进行折半查找,假设终止点为E,则可以将二维数组位于终止点E右边的矩形Rr排除,因为终止点E小于其右边相邻的元素E+1,而E+1是右边矩形Rr的最小元素(左上元素)
同理,对下边界bottom折半,可以排除二维数组位于终止点E左边的矩形Rl排除,
对左边界left折半,可以排除二维数组位于终止点E下边的矩形Rb排除,
对右边界right折半,可以排除二维数组位于终止点E上边的矩形Rt排除,
一轮过去,边界范围缩小,对由新边界组成的矩形重复以上操作,直到范围缩小为只有一个元素

三、N行折半法:
也是很容易就能想到的方法,每一行(列)都执行折半查找,持续M行(列),复杂度为O(M*logN)

四(一)、简单的十字分割法:
亲测可用,使用十字将数组等分为四个区域,中间交叉点用来判断,因为左上区域和右下区域必然冲突,所以二者必然可以排除一个,即每次至少可以排除1/4的数据,接着对剩下的三个区域进行递归操作,当然可以利用左上右下元素来判断是否落在区域内来进行优化,直到只有一个元素,递归层数为:logN,不优化的话最底层节点数最多为3^logN,优化后节点数会少很多,实测效率还算不错

四(二)、另一版本的十字分割法
与上一个类似,只不过这里先在主对角线方向上进行折半查找操作,操作结束后,终止点的左上区域与右下区域都可以排除,这样就只剩下左下、右上两个区域,然后再进行递归。感觉上效率会比上个版本好,不过这个我没有去实现验证

五、暴力法
不解释,复杂度为O(M*N)

下面给出双折半查找法的代码:

class Solution {
public:
    bool Find(int target, vector<vector<int> > array) {
        if (array.size()==0 || array[0].size()==0)
            return false;
        else {
            int top = 0, bottom = array.size()-1;
            int left = 0, right = array[0].size()-1;
            int sLeft = 0, sRight = 0;
            int sTop = 0, sBottom = 0;
            int mid = 0;

            for (;left<right || top<bottom;) {
                //对上边界进行折半,可以缩小右边界
                sLeft = left, sRight = right;
                for (;sLeft<=sRight;) {
                    mid = (sLeft + sRight) / 2;
                    if (array[top][mid]==target) return true;
                    else if (array[top][mid]<target) sLeft = mid+1;
                    else sRight = mid-1;
                }
                if (mid<right) right = mid; //利用终止点缩小右边界
                top++;

                //对下边界进行折半,可以缩小左边界
                sLeft = left, sRight = right;
                for (;sLeft<=sRight;) {
                    mid = (sLeft + sRight) / 2;
                    if (array[bottom][mid]==target) return true;
                    else if (array[bottom][mid]<target) sLeft = mid+1;
                    else sRight = mid-1;
                }
                if (left<mid) left = mid; //利用终止点缩小左边界
                bottom--;

                //对左边界进行折半,可以缩小下边界
                sTop = top, sBottom = bottom;
                for (;sTop<=sBottom;) {
                    mid = (sTop + sBottom) / 2;
                    if (array[mid][left]==target) return true;
                    else if (array[mid][left]<target) sTop = mid+1;
                    else sBottom = mid-1;
                }
                if (mid<bottom) bottom = mid; //利用终止点缩小下边界
                left++;

                //对右边界进行折半,可以缩小上边界
                sTop = top, sBottom = bottom;
                for (;sTop<=sBottom;) {
                    mid = (sTop + sBottom) / 2;
                    if (array[mid][right]==target) return true;
                    else if (array[mid][right]<target) sTop = mid+1;
                    else sBottom = mid-1;
                }
                if (top<mid) top = mid; //利用终止点缩小上边界
                right--;
            }
            if (array[top][left]==target) return true;
            else return false;
        }
    }
};

下面是简单的十字分割法代码:

class Solution {
public:
    bool Find(int target, vector<vector<int> > array) {
        if (array.size()==0 || array[0].size()==0)
            return false;
        else {
            return find_by_ten_shape_divide(array, target, 0, 0, array[0].size()-1, array.size()-1);
        }
    }

    bool find_by_ten_shape_divide(vector<vector<int> > &datas, int target, int left, int top, int right, int bottom) {
        // 递归终止的条件:当区域被分割为只有一个元素
        if (left==right && top==bottom) {
            if (datas[top][left] == target)
                return true;
            else return false;
        }
        // 利用头尾数据进行优化:不在区域内的直接false,可以减少递归节点数量
        else if (datas[top][left]>target || datas[bottom][right]<target)
            return false;
        else {
            // 水平、竖直方向各自等分
            int cHoriz = (left + right) / 2;
            int cVertic = (top + bottom) / 2;

            if (datas[cVertic][cHoriz] == target)
                return true;
            else {
                // 目标大于分割点则排除左上区域,对右下进行递归
                if (datas[cVertic][cHoriz] < target) {
                    if (cHoriz<right && cVertic<bottom &&
                        find_by_ten_shape_divide(datas, target, cHoriz+1, cVertic+1, right, bottom))
                        return true;
                }
                // 否则排除右下区域,对左上进行递归
                else if ((left<cHoriz || top<cVertic) &&
                         find_by_ten_shape_divide(datas, target, left, top, cHoriz, cVertic))
                    return true;
                // 对右上区域进行递归
                if (cHoriz < right &&
                    find_by_ten_shape_divide(datas, target, cHoriz+1, top, right, cVertic))
                    return true;
                // 对左下区域进行递归
                if (cVertic < bottom &&
                    find_by_ten_shape_divide(datas, target, left, cVertic+1, cHoriz, bottom))
                    return true;
                return false;
            }
        }
    }
};
全部评论
这代码量略大啊
点赞 回复 分享
发布于 2019-09-02 16:36
二维数组终止点E指的是啥?
点赞 回复 分享
发布于 2019-11-01 21:42
左上右下 不是O(n+m)吗?
点赞 回复 分享
发布于 2020-02-17 08:16
请问大神,if (array.size()==0 || array[0].size()==0)这一句,为什么,我只写if (array.size()==0)通不过呢?我的理解是如果array.size()==0的话,不就是这个二维数组为空的充要条件了吗?困扰许久,拜托大神指点迷津啦~!
点赞 回复 分享
发布于 2020-03-15 15:59
双折不应该算是二分查找么?它的时间复杂度不应该要比左上右下低么?
点赞 回复 分享
发布于 2020-04-01 20:45
你这个写的也太多了,太可怕了,
点赞 回复 分享
发布于 2020-06-22 11:25
代码多的我看到就怕
点赞 回复 分享
发布于 2020-08-05 17:05
大佬你好,我的代码和你的双折半查找法差不多,都是四个边界往小缩,不过我没有用二分法查找,结果就超时了。您可以帮我看下代码哪里的问题吗😔我直接暴力都能过,这个O(m+n)的竟然超时。代码在我博客里。
点赞 回复 分享
发布于 2020-09-05 12:25
请教一下,这个边界判断必须是楼主写的这样吗?我的理解是,不论 col 还是 row在哪个if下+1或者-1,只要能把这个矩阵都覆盖到就可以,比如我倾向于左上和右下判断时就包含row和col,对于右上和坐下,则是不包含row和col的部分。但是这样会堆溢出。。
点赞 回复 分享
发布于 2020-09-10 21:54
代码多的我看到就怕
点赞 回复 分享
发布于 2021-06-01 11:20

相关推荐

Noob1024:一笔传三代,人走笔还在
点赞 评论 收藏
分享
42 2 评论
分享
牛客网
牛客企业服务