【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)。
相关推荐
rainbow68891 天前
深入解析C++STL:map与set底层奥秘
java·数据结构·算法
觉醒大王1 天前
AI写的青基中了
人工智能·笔记·深度学习·学习·职场和发展·学习方法
wangjialelele1 天前
平衡二叉搜索树:AVL树和红黑树
java·c语言·开发语言·数据结构·c++·算法·深度优先
驱动探索者1 天前
linux mailbox 学习
linux·学习·算法
ringking1231 天前
autoware-1:安装环境cuda/cudnn/tensorRT库函数的判断
人工智能·算法·机器学习
程序员敲代码吗1 天前
面试中sessionStorage问题引发深度探讨
面试·职场和发展
大闲在人1 天前
8. 供应链与制造过程术语:产能
算法·制造·供应链管理·智能制造·工业工程
橘颂TA1 天前
【测试】高效浏览器操作:基础功能与优化设置大全
c++·功能测试·职场和发展·测试·web测试
一只小小的芙厨1 天前
寒假集训笔记·以点为对象的树形DP
c++·算法
历程里程碑1 天前
普通数组----合并区间
java·数据结构·python·算法·leetcode·职场和发展·tornado