剑指 Offer 04. 二维数组中的查找

剑指 Offer 04. 二维数组中的查找 - 力扣(LeetCode) (leetcode-cn.com)

目录

方案1

运行结果

思路

代码(含注释)

方案2

运行结果

思路

代码


方案1

运行结果

思路

矩阵的特点是行列均有序,对于这种矩阵,我们在查找时,不能确定target一定出现在哪片区域,但可以确定target一定不会出现在哪片区域。

target一定不会出现在哪片区域?随便找到一个元素x,如果x < target,那么它的左上方、以它为右下角的矩阵中的元素均小于等于x,该矩阵中一定没有target;如果x > target,那么它的右下方、以它为左上角的矩阵中的元素均大于等于x,该矩阵中一定没有target。

而不论x比target大还是小,x的左下方矩阵、x的右上方矩阵均为target可能出现的地方。

基于上述特点,我们不难想出一种算法:每次把矩阵划分为四块,排除掉左上和右下部分,对左下和右上部分递归进行划分和排除操作,直到矩阵的规模足够小,我们就能直接遍历整个矩阵来查找target。具体分析如下:

根据矩阵的特点,矩阵的主对角线也满足递增的顺序,因此如果target不出现在对角线上的话,我们可以用target来划分矩阵的主对角线,即找到两个位置center0和center1满足:

1. center1在center0之下,且在矩阵对角线构成的这个有序数列中,center0与center1相邻

2. center0位置的元素小于target,center1位置上的元素大于target

进一步,我们可以用center0和center1来将矩阵划分为四个部分,分别是:

1. top_left_matrix:原矩阵的左上角,以center0位置为右下角的矩阵,其中元素均小于等于center0位置的元素

2. bottem_right_matrix:原矩阵的右下角,以center1位置为左上角的矩阵,其中元素均大于等于center1位置的元素

3. bottem_left_matrix && top_right_matrix:矩阵中剩下的两个部分,即原矩阵的左下角部分和右上角部分,这里两部分中的元素与target的大小关系不确定

我们知道target一定不会出现在左上角和右下角矩阵中,因此只需递归划分左下角和右上角矩阵即可。

当矩阵小于一定规模(MIN_SIZE)时,我们不再继续划分,而是改为直接遍历整个矩阵,暴力查找target。MIN_SIZE的具体值需根据测试用例的规模来具体设定,上图所示的运行结果是将MIN_SIZE设置为2时达到的。

代码(含注释)

class pos { //该类用来记录矩阵中元素的位置,x为行坐标,y为列坐标
public:
    int x, y;
    pos() : x(0), y(0) {}
    pos(int _x, int _y) : x(_x), y(_y) {}
    bool get_center(pos&, pos&);    //取得两个位置的中点,即以这两个位置为左上顶点和右下顶点的矩阵的中心
    pos& operator=(const pos&);
};

bool pos::get_center(pos& cntr0, pos& cntr1) {
    if (cntr1.x - cntr0.x <= 1 && cntr1.y - cntr0.y <= 1) return false; //当这两点位置已经贴近到没有中点时,返回false
    this->x = cntr0.x + (cntr1.x - cntr0.x) / 2;
    this->y = cntr0.y + (cntr1.y - cntr0.y) / 2;
    return true;
}

pos& pos::operator=(const pos& p) {
    this->x = p.x;
    this->y = p.y;
    return *this;
}

class Solution {
#define MIN_SIZE 20     //根据测试用例来设定停止划分时矩阵的规模
public:
    bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
        if (matrix.size() == 0 || matrix[0].size() == 0) return false;

        pos tl(0, 0), br(matrix.size() - 1, matrix[0].size() - 1);  //tl即top_left,左上角;br即bottem_right,右下角
        return _find(matrix, tl, br, target);
    }

    bool _find(vector<vector<int>>& matrix, pos tl, pos br, int target) {
        if (br.x - tl.x > MIN_SIZE || br.y - tl.y > MIN_SIZE) {
            if (matrix[tl.x][tl.y] == target || matrix[br.x][br.y] == target) return true;

            pos cntr0 = tl, cntr1 = br, cntr;   //cntr即center
            while (cntr.get_center(cntr0, cntr1)) { // 进行二分查找,直到center0和center1已经贴近
                if (matrix[cntr.x][cntr.y] == target) return true;

                matrix[cntr.x][cntr.y] < target ? cntr0 = cntr : cntr1 = cntr;
            }

            pos bl_tl(cntr1.x, tl.y), bl_br(br.x, cntr0.y);     //bl即bottem_left,bl_tl为左下角矩阵的左上角位置,bl_br为左下角矩阵的右下角位置
            pos tr_tl(tl.x, cntr1.y), tr_br(cntr0.x, br.y);     //tr即top_right,tr_tl为右上角矩阵的左上角位置,tr_br为右上角矩阵的右下角位置
            return _find(matrix, bl_tl, bl_br, target) || _find(matrix, tr_tl, tr_br, target);
        }
        else {  //暴力查找
            for (int i = tl.x; i <= br.x; ++i) {
                for (int j = tl.y; j <= br.y; ++j) {
                    if (matrix[i][j] == target) return true;
                }
            }
            return false;
        }
    }
};

方案2

运行结果

思路

 考虑矩阵的右上角元素,该元素既是第一行最大的元素,又是最后一列最小的元素。如果该元素比target小,那么第一行所有元素均比target小,即可排除第一行所有元素;如果该元素比target大,那么最后一列所有元素均比target大,即可排除最后一列所有元素。因此每次只需检查矩阵右上角元素,就能不断缩小要查找的矩阵规模,最终找到target。

代码

class Solution {
public:
    bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
        if (matrix.size() == 0 || matrix[0].size() == 0) return false;

        int m = matrix.size() - 1, n = matrix[0].size() - 1;
        int i = 0, j = n;
        while (i <= m && j >= 0) {
            if (matrix[i][j] == target) return true;
            matrix[i][j] > target ? --j : ++i;
        }

        return false;
    }
};

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值