文章目录
1. 题目信息
1.1 题目描述
题目链接: 剑指 Offer 04. 二维数组中的查找
在一个 m * n 的二维数组中,每一行都按照从左到右递增的顺序排序,每一列都按照从上到下递增的顺序排序。
请完成一个高效的函数,输入这样的一个二维数组和一个整数,判断数组中是否含有该整数。
限制:
0 <= n <= 1000
0 <= m <= 1000
注意:本题与主站 240 题相同:https://leetcode-cn.com/problems/search-a-2d-matrix-ii/
1.2 测试用例
现有矩阵 matrix 如下:
[
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19],
[3, 6, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
- 给定 target = 5,返回 true。
- 给定 target = 20,返回 false。
2. 题目分析
2.1 暴力遍历
给定的二维数组较小, 两层循环直接遍历也可以, 是最基础的解法, 时间复杂度 O ( N 2 ) O(N^2) O(N2);
2.2 行遍历+列二分
要充分利用一下行列有序的 信息, 结合数据结构, 按行遍历, 每一行进行二分即可求解;
- 在
C++
中可以使用binary_search/uppper_bound
等<algorithm>
中的函数;
2.3 改进的行上限+列二分
在上一个解决方案上, 行是逐个遍历的, 不知道数据在哪一行, 平均下来找行 N 2 {N} \over {2} 2N 次 ; 那么可不可以不处理某些行呢? 理论上是有优化空间的, 比如如下数据:
matrix = [
[1, 4, 7, 11, 15],
[1, 4, 7, 11, 15],
[1, 4, 7, 11, 15],
[2, 5, 8, 12, 19], // 从这里找
[6, 7, 9, 16, 22],
[10, 13, 14, 17, 24],
[18, 21, 23, 26, 30]
]
target = 5
- 可以先通过
upper_bound
找出首个数字大于等于5的那行, 然后倒着找, 这个时间复杂度不好分析, 大概是 O ( l o g 2 N + X ‾ ) ≈ O ( l o g 2 N + N 4 ) O(log_{2}N+\overline{X}) \approx O(log_{2}N+{{N} \over {4}}) O(log2N+X)≈O(log2N+4N) , 其中 X ‾ \overline{X} X 表示找出的行上界的位置, 值接近 N 2 {{N} \over {2}} 2N;
2.4 左下角或者右上角开始 行列交替遍历
根据数据的有序, 每次戴比较的下标有两个方向可以移动, 复杂度 O ( M + N ) O(M+N) O(M+N)
- 以
左下角
开始遍历为例, 比较当前下标数字大于target则row--, 小于target则col++
; - 以
右上角
开始遍历为例, 比较当前下标数字大于target则col--, 小于target则row++
;
3. 代码详情
3.1 C++
3.1.1 暴力
// 执行用时:24 ms, 在所有 C++ 提交中击败了89.90%的用户
// 内存消耗:12.8 MB, 在所有 C++ 提交中击败了6.11%的用户
class Solution {
public:
bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
for (auto &row: matrix) {
for (auto x: row) {
if (x == target) {
return true;
}
}
}
return false;
}
}
3.1.2 行遍历+列二分
class Solution {
public:
bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
// 执行用时:24 ms, 在所有 C++ 提交中击败了89.90%的用户
// 内存消耗:12.7 MB, 在所有 C++ 提交中击败了28.17%的用户
int m = matrix.size();
if (m == 0) {
return false;
}
int n = matrix[0].size();
if (n == 0) {
return false;
}
for (int i = 0; i < m; i++) {
if (binary_search(matrix[i].begin(), matrix[i].end(), target)) {
return true;
}
}
return false;
}
}
3.1.3 改进的行上限+列二分
// 用于 lower_bound
bool CompareFirstLess(const vector<int>& a, int target) {
return a[0] < target;
}
// 用于 upper_bound
bool CompareFirstGreat(int target, const vector<int>& b) {
return b[0] > target;
}
class Solution {
public:
bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
int m = matrix.size();
if (m == 0) {
return false;
}
int n = matrix[0].size();
if (n == 0) {
return false;
}
auto it = upper_bound(matrix.begin(), matrix.end(), target, CompareFirstGreat);
if (it == matrix.end()) {
// 注意边界, 找不到比他大的, 返回 end
it--;
}
cout << *it->begin() << endl;
while (it >= matrix.begin()) {
if (binary_search(it->begin(), it->end(), target)) {
return true;
}
it--;
}
return false;
}
};
3.1.4 左下角或者右上角开始 行列交替遍历
// 执行用时:24 ms, 在所有 C++ 提交中击败了89.90%的用户
// 内存消耗:12.8 MB, 在所有 C++ 提交中击败了6.11%的用户
class Solution {
public:
bool findNumberIn2DArray(vector<vector<int>>& matrix, int target) {
if (matrix.size() == 0 || matrix[0].size() == 0) {
return false;
}
int rows = matrix.size();
int cols = matrix[0].size();
int row = 0, col = cols - 1;
while (row < rows && col >= 0) {
if (matrix[row][col] == target) {
return true;
} else if (matrix[row][col] > target) {
col--;
} else {
row++;
}
}
return false;
}
};
3.2 Python
代码略 ~, 后续有时间再补充吧 😃
4. 总结
数据量太小, 时间复杂度差别不大, 仅做参考。