剑指 Offer 04. 二维数组中的查找 - 力扣(LeetCode) (leetcode-cn.com)
目录
方案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;
}
};