《剑指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

相关推荐

头像 会员标识
11-27 17:08
已编辑
牛客_产品运营部_私域运营
腾讯 普通offer 24k~26k * 15,年包在36w~39w左右。
点赞 评论 收藏
分享
Yushuu:你的确很厉害,但是有一个小问题:谁问你了?我的意思是,谁在意?我告诉你,根本没人问你,在我们之中0人问了你,我把所有问你的人都请来 party 了,到场人数是0个人,誰问你了?WHO ASKED?谁问汝矣?誰があなたに聞きましたか?누가 물어봤어?我爬上了珠穆朗玛峰也没找到谁问你了,我刚刚潜入了世界上最大的射电望远镜也没开到那个问你的人的盒,在找到谁问你之前我连癌症的解药都发明了出来,我开了最大距离渲染也没找到谁问你了我活在这个被辐射蹂躏了多年的破碎世界的坟墓里目睹全球核战争把人类文明毁灭也没见到谁问你了😆
点赞 评论 收藏
分享
评论
42
2
分享
牛客网
牛客企业服务