【LeetCode 每日一题】1292. 元素和小于等于阈值的正方形的最大边长

Problem: 1292. 元素和小于等于阈值的正方形的最大边长

文章目录

  • [1. 整体思路](#1. 整体思路)
  • [2. 完整代码](#2. 完整代码)
  • [3. 时空复杂度](#3. 时空复杂度)
      • [时间复杂度: O ( M × N ) O(M \times N) O(M×N)](#时间复杂度: O ( M × N ) O(M \times N) O(M×N))
      • [空间复杂度: O ( M × N ) O(M \times N) O(M×N)](#空间复杂度: O ( M × N ) O(M \times N) O(M×N))

1. 整体思路

核心问题

在一个矩阵中,找到一个边长最大的正方形,使得该正方形内所有元素的和小于等于给定的 threshold

算法逻辑

  1. 预处理:二维前缀和 (2D Prefix Sum)

    • 为了在 O ( 1 ) O(1) O(1) 时间内计算出任意子矩形(正方形)的元素和,代码首先构建了一个辅助数组 sum
    • sum[i][j] 表示原矩阵中从 (0, 0)(i-1, j-1) 矩形区域的元素总和。
    • 利用容斥原理公式:当前格前缀和 = 上 + 左 - 左上 + 当前元素值
  2. 主逻辑:枚举位置 + 贪心扩展 (Smart Iteration)

    • 这也是这段代码最巧妙的地方。
    • 我们使用两个循环遍历矩阵的每个位置 (i, j),将其视为正方形的左上角
    • 维护一个变量 ans,表示当前已经找到的最大边长
    • 在遍历每个位置时,我们不需要 从边长 1 开始重新检查,也不需要二分查找边长。
    • 我们只需要检查:从当前位置 (i, j) 开始,能否形成一个边长为 ans + 1 的正方形,且其元素和 ≤ \le ≤ threshold
      • 如果能 :说明我们找到了一个更大的正方形,于是执行 ans++。并在当前位置继续尝试 ans + 2(由 while 循环实现,尽管实际上每次位置移动通常最多增加 1,但在某些特殊情况下可能连续增加)。
      • 如果不能 :说明以当前 (i, j) 为起点的正方形无法打破当前的记录 ans,直接继续遍历下一个位置。
    • 这种策略保证了 ans单调递增的。我们只关心是否能找到比当前更大的正方形,忽略比当前小或相等的。

2. 完整代码

java 复制代码
class Solution {
    public int maxSideLength(int[][] mat, int threshold) {
        int m = mat.length;
        int n = mat[0].length;
        
        // 1. 构建二维前缀和数组
        // 大小设为 [m+1][n+1] 是为了方便处理边界(第0行和第0列全为0)
        int[][] sum = new int[m + 1][n + 1];
        
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                // 前缀和递推公式:上 + 左 - 左上 + 当前元素
                // 注意:sum 数组的索引比 mat 数组大 1
                sum[i + 1][j + 1] = sum[i + 1][j] + sum[i][j + 1] - sum[i][j] + mat[i][j];
            }
        }

        // ans 记录全局找到的最大边长
        int ans = 0;
        
        // 2. 遍历矩阵的每个格子,作为正方形的左上角
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                // 核心贪心逻辑:
                // 检查以 (i, j) 为左上角,边长为 ans + 1 的正方形是否合法。
                // 这里的 i + ans 其实对应的是边长 ans + 1 的右下角坐标偏移量。
                // 举例:若 ans=0,检查 i 到 i+0 (长度1);若 ans=1,检查 i 到 i+1 (长度2)。
                
                // 边界检查 (i + ans < m && j + ans < n) 确保正方形不越界
                // 阈值检查 (getRectSum(...) <= threshold) 确保元素和满足条件
                while (i + ans < m && j + ans < n && 
                       getRectSum(sum, i, j, i + ans, j + ans) <= threshold) {
                    // 如果满足条件,说明找到了一个更大的正方形 (边长 ans+1)
                    // 更新 ans,并尝试继续扩大(while 循环会继续检查 ans+2)
                    ans++;
                }
            }
        }
        return ans;
    }

    // 辅助方法:利用前缀和在 O(1) 时间内计算子矩形的元素和
    // (r1, c1) 是左上角, (r2, c2) 是右下角 (包含边界)
    private int getRectSum(int[][] sum, int r1, int c1, int r2, int c2) {
        // sum 数组中索引偏移了 1,所以右下角取 r2+1, c2+1
        // 左上角要减去的部分对应的索引是 r1, c1 (也就是原图索引 r1, c1 对应的 sum 数组位置)
        return sum[r2 + 1][c2 + 1] - sum[r2 + 1][c1] - sum[r1][c2 + 1] + sum[r1][c1];
    }
}

3. 时空复杂度

假设矩阵的行数为 M M M,列数为 N N N。

时间复杂度: O ( M × N ) O(M \times N) O(M×N)

  • 前缀和构建 :遍历整个矩阵一次,耗时 O ( M × N ) O(M \times N) O(M×N)。
  • 主循环与贪心检查
    • 虽然有双重 for 循环和一个内部的 while 循环,但我们可以这样分析:
    • 双重 for 循环会访问每个格子 (i, j) 一次。
    • 在每次访问时,getRectSum 的计算是 O ( 1 ) O(1) O(1) 的。
    • 关键在于 ans 变量。ans 在整个程序的生命周期中只增不减ans 的最大值也就限制在 min ⁡ ( M , N ) \min(M, N) min(M,N)。
    • 也就是说,while 循环中的 ans++ 操作总共最多执行 min ⁡ ( M , N ) \min(M, N) min(M,N) 次。
    • 大部分情况下,while 循环的条件判断只会执行一次(失败并退出)或者少数几次(成功并增加 ans)。
    • 因此,均摊下来,总操作次数与格子数量成正比。
  • 结论 : O ( M × N ) O(M \times N) O(M×N)。这是此问题的最优解法。

空间复杂度: O ( M × N ) O(M \times N) O(M×N)

  • 计算依据
    • 我们需要一个大小为 ( M + 1 ) × ( N + 1 ) (M+1) \times (N+1) (M+1)×(N+1) 的二维数组 sum 来存储前缀和。
  • 结论 : O ( M × N ) O(M \times N) O(M×N)。
相关推荐
Remember_9932 小时前
【数据结构】深入理解Map和Set:从搜索树到哈希表的完整解析
java·开发语言·数据结构·算法·leetcode·哈希算法·散列表
浅念-2 小时前
C++第一课
开发语言·c++·经验分享·笔记·学习·算法
charlie1145141912 小时前
现代嵌入式C++教程:对象池(Object Pool)模式
开发语言·c++·学习·算法·嵌入式·现代c++·工程实践
燃于AC之乐2 小时前
我的算法修炼之路--8——预处理、滑窗优化、前缀和哈希同余,线性dp,图+并查集与逆向图
算法·哈希算法·图论·滑动窗口·哈希表·线性dp
格林威2 小时前
多相机重叠视场目标关联:解决ID跳变与重复计数的 8 个核心策略,附 OpenCV+Halcon 实战代码!
人工智能·数码相机·opencv·算法·计算机视觉·分类·工业相机
郝学胜-神的一滴2 小时前
深入理解网络分层模型:数据封包与解包全解析
linux·开发语言·网络·程序人生·算法
永远都不秃头的程序员(互关)2 小时前
【K-Means深度探索(九)】K-Means与数据预处理:特征缩放与降维的重要性!
算法·机器学习·kmeans
源代码•宸2 小时前
Golang原理剖析(逃逸分析)
经验分享·后端·算法·面试·golang··内存逃逸
重生之后端学习3 小时前
25. K 个一组翻转链表
java·数据结构·算法·leetcode·职场和发展