[解题报告]《剑指offer》04 -- 二维数组中的查找

博客详细介绍了如何在一个按行、列递增排序的二维数组中查找目标数值。提供了四种解法,包括暴力遍历、行遍历加列二分、改进的行上限加列二分以及行列交替遍历,分别分析了它们的时间复杂度和应用场景。并给出了C++代码实现,展示了不同方法的效率对比。
摘要由CSDN通过智能技术生成

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. 总结

数据量太小, 时间复杂度差别不大, 仅做参考。

评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

逸云沙鸥のIHave@Dream

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值