二分查找专题(九):“降维”的魔术!将二维矩阵“拉平”为一维

哈喽各位,我是前端小L。

欢迎来到我们的二分查找专题第九篇!我们刚从"旋转数组"的"二义性"泥潭(LC 81)中爬出来,今天,我们面对一个看似更复杂的二维矩阵。

但是,请仔细阅读题目的"附加属性":

  1. 每行中的整数从左到右按升序排列。 (这个很常见)

  2. 每行的第一个整数大于前一行的最后一个整数。 (这,就是"Aha!"时刻!)

这个第二条属性 ,是出题人送给我们的"大礼包"!它意味着,如果我们把这个二维矩阵,一行一行地"首尾相连",它会变成一个完美、巨大、且完全有序一维数组

[row_0_elements, row_1_elements, row_2_elements, ...]

这道"二维"问题,被我们瞬间"降维 "成了我们专题第一篇(LC 704)的"Hello, World!"问题!

力扣 74. 搜索二维矩阵

https://leetcode.cn/problems/search-a-2d-matrix/

题目分析:

  • 输入 :一个 m x n 矩阵,具有上述两条"强有序"属性。一个 target

  • 目标 :判断 target 是否存在。

  • 约束:高效。(暗示 O(log(m*n)))

核心洞察: 我们不需要真的创建一个 O(m*n) 的新数组。我们可以"虚拟"地在这个"拉平后"的数组上进行二分查找。

"万能模板"的"虚拟"应用

我们的"左闭右开 [left, right) 万能模板"再次登场!

1. 区间定义 (一维虚拟化)

  • 我们的"虚拟数组"总共有 totalElements = m * n 个元素。

  • 它的索引范围是 [0, m*n - 1]

  • 套用我们的模板,搜索区间 [left, right) 就是 [0, m * n)

  • left = 0

  • right = m * n

2. mid 的"二维映射" (关键技巧)

  • mid = left + (right - left) / 2。这个 mid 是一个 0m*n - 1 之间的一维"虚拟索引"。

  • 我们如何把它映射回二维矩阵的 (row, col) 坐标?

  • 假设矩阵有 n 列(cols)。

  • row = mid / n (整除 n 得到行号)

  • col = mid % n (模 n 得到列号)

3. 状态转移 (标准模板)

  • 得到 mid 对应的 midVal = matrix[row][col]

  • if (midVal == target):找到了,return true

  • else if (midVal < target):目标在右侧,left = mid + 1

  • else (midVal > target):目标在左侧,right = mid

代码实现

复制代码
#include <vector>

using namespace std;

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

        int m = matrix.size();    // 行数
        int n = matrix[0].size(); // 列数
        
        // 1. 区间定义:[left, right) -> [0, m*n)
        int left = 0;
        int right = m * n; // 总元素个数,作为开区间

        // 2. 循环条件
        while (left < right) {
            // 3. mid 计算
            int mid = left + (right - left) / 2;

            // 4. 将一维 mid 映射回二维 (row, col)
            int row = mid / n;
            int col = mid % n;
            int midVal = matrix[row][col];

            // 5. 标准的指针移动
            if (midVal == target) {
                return true;
            } else if (midVal < target) {
                // 目标在右侧 [mid + 1, right)
                left = mid + 1;
            } else { // midVal > target
                // 目标在左侧 [left, mid)
                right = mid;
            }
        }
        
        // 循环结束,没找到
        return false;
    }
};

深度复杂度分析

  • 时间复杂度 O(log(m*n))

    • 我们的搜索空间是 m * n 个元素。

    • 每一次循环,都将搜索空间缩小一半。

    • 总的比较次数是对 m * n 取对数,即 O(log(m*n))。

    • log(m*n) = log(m) + log(n),所以写成 O(log m + log n) 也是完全正确的)。

  • 空间复杂度 O(1)

    • 我们没有创建那个 m*n 的虚拟数组,只使用了 left, right, mid, m, n, row, col 等常数个额外变量。

总结

今天,我们通过一次巧妙的"降维 ",把一个"二维"问题,转化成了一个"一维"问题。 row = mid / colscol = mid % cols 这对"映射"公式,是你务必掌握的"魔法"。

但是,请注意! 这个解法之所以成立 ,全拜那条"每行开头大于上行末尾 "的黄金属性所赐。 如果一个二维矩阵,没有 这条属性,仅仅是"每行有序,每列有序",那它就不能被"拉平"了。

在下一篇中,我们将直面那个真正的、无法"降维"的二维矩阵搜索问题 (LC 240),看看那时我们又该如何应对。

下期见!

相关推荐
Jasmine_llq2 小时前
《P7516 [省选联考 2021 A/B 卷] 图函数》
算法·弗洛伊德算法·floydwarshall算法·后缀和计算
kaikaile19952 小时前
三维CT图像重建算法
算法
她说人狗殊途2 小时前
时间复杂度(按增长速度从低到高排序)包括以下几类,用于描述算法执行时间随输入规模 n 增长的变化趋势:
数据结构·算法·排序算法
计科土狗2 小时前
算法基础入门第一章
c++·算法
与己斗其乐无穷2 小时前
算法(二)滑动窗口
算法
Ghost-Face2 小时前
恭喜自己,挑战成功!
算法
Miraitowa_cheems2 小时前
LeetCode算法日记 - Day 102: 不相交的线
数据结构·算法·leetcode·深度优先·动态规划
野生技术架构师2 小时前
盘一盘Redis的底层数据结构
数据结构·数据库·redis
ByteX2 小时前
算法练习-成功之后不许掉队
算法