[H前缀和] lc363. 矩形区域不超过 K 的最大数值和(二维前缀和+lower_bound()+好题)

1. 题目来源

链接:363. 矩形区域不超过 K 的最大数值和

相关题目:[线性dp] 最强对手矩阵(牛客+线性dp+二维前缀和+代码优化+思维+好题)

2. 题目解析

很基础的一道二维前缀和问题。暴力的话需要枚举所有矩阵,再计算的话就需要 O ( n 3 m 3 ) O(n^3m^3) O(n3m3) 的时间复杂度。配合二维前缀和可以到 O ( n 2 m 2 ) O(n^2m^2) O(n2m2) 的时间复杂度。但是对于本题还是不能通过。我们需要 O ( n 2 m ) O(n^2m) O(n2m) 或者 O ( n 2 m l o g m ) O(n^2mlogm) O(n2mlogm) 的时间复杂度。

思路:

  • 预处理二维前缀和,用以 O ( 1 ) O(1) O(1) 求解子矩阵的和。
  • 枚举子矩阵上边界 x1、下边界 x2、顺序遍历右边界 y2。这就已经 O ( n 2 m ) O(n^2m) O(n2m) 的时间复杂度了。
  • set 里面存 (x1,1)---(x2,y2) 这个子矩阵的和,记为 si,其中 si 为固定值,若想使 si-sj<=k 最大,那么就需要让 sj 尽量小,等价于 sj>=si-k。运用 lower_bound() 的操作得到第一个大于等于 si-k 的位置,其值为 t,那么就有 t>=si-k,就有 si-t<=k 了。
  • 故,si-t 就是当前右边界下的满足的最大答案,枚举完毕所有右边界,答案每次取最大值即可。注意需要将 s[0] 先插入,因为对于前缀和来讲 sj 是从 0 枚举到 n-1i 是从 1 枚举到 n 的。
  • 这个先插入 0 是非常必要的,因为前缀和数组是要从 0 开始枚举的,为了处理边界问题。
  • set 内部基于平衡树查找,时间复杂度 O ( l o g m ) O(logm) O(logm),故本题的总时间复杂度就是 O ( n 2 m l o g m ) O(n^2mlogm) O(n2mlogm)。100^3 * 7 = 7e6 的计算次数,还是可以过的。
  • 显然,当 n 远大于 m 的时候,就可以将 nm 进行调换,复杂度会大大降低。

当然,本题采用 列压缩,再枚举上下边界也是可以的。这样空间复杂度就能降至 O ( n ) O(n) O(n),具体可参考官方题解。

前缀和数组元素非负时单调递增,但是本题有负值存在,不可直接进行二分,需要维护一个 set 帮助二分。


  • 时间复杂度 O ( n 2 m l o g m ) O(n^2mlogm) O(n2mlogm)
  • 空间复杂度 O ( n m ) O(nm) O(nm)

代码:

class Solution {
public:
    int maxSumSubmatrix(vector<vector<int>>& matrix, int k) {
        int n = matrix.size(), m = matrix[0].size();
        vector<vector<int>> s(n + 1, vector<int>(m + 1));

        for (int i = 1; i <= n; i ++ )
            for (int j = 1; j <= m; j ++ )
                s[i][j] = s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1] + matrix[i - 1][j - 1];
        
        int res = -1e9;
        for (int x1 = 1; x1 <= n; x1 ++ ) 
            for (int x2 = x1; x2 <= n; x2 ++ ) {
                set<int> S;
                S.insert(0);
                for (int y2 = 1; y2 <= m; y2 ++ ) {
                    int si = s[x2][y2] - s[x1 - 1][y2] - s[x2][0] + s[x1 - 1][0];
                    auto it = S.lower_bound(si - k);
                    if (it != S.end()) res = max(res, si - *it);
                    S.insert(si);
                }
            }
        return res;
    }
};

二维前缀和优秀的写法:

class Solution {
public:
    vector<vector<int>> s;

    int get(int x1, int y1, int x2, int y2) {
        return s[x2][y2] - s[x1 - 1][y2] - s[x2][y1 - 1] + s[x1 - 1][y1 - 1];
    }

    int maxSumSubmatrix(vector<vector<int>>& matrix, int k) {
        int n = matrix.size(), m = matrix[0].size();
        s = vector<vector<int>>(n + 1, vector<int>(m + 1));
        for (int i = 1; i <= n; i ++ )
            for (int j = 1; j <= m; j ++ )
                s[i][j] = matrix[i - 1][j - 1] + s[i - 1][j] + s[i][j - 1] - s[i - 1][j - 1];

        int res = INT_MIN;
        for (int x1 = 1; x1 <= n; x1 ++ )
            for (int x2 = x1; x2 <= n; x2 ++ ) {
                set<int> S;
                S.insert(0);
                for (int y2 = 1; y2 <= m; y2 ++ ) {
                    int si = get(x1, 1, x2, y2);
                    auto it = S.lower_bound(si - k);
                    if (it != S.end()) res = max(res, si - *it);
                    S.insert(si);
                }
            }
        return res;
    }
};

列压缩写法:

来自官方题解代码!

class Solution {
public:
    int maxSumSubmatrix(vector<vector<int>> &matrix, int k) {
        int ans = INT_MIN;
        int m = matrix.size(), n = matrix[0].size();
        for (int i = 0; i < m; ++i) { // 枚举上边界
            vector<int> sum(n);
            for (int j = i; j < m; ++j) { // 枚举下边界
                for (int c = 0; c < n; ++c) {
                    sum[c] += matrix[j][c]; // 更新每列的元素和
                }
                set<int> sumSet{0};
                int s = 0;
                for (int v : sum) {
                    s += v;
                    auto lb = sumSet.lower_bound(s - k);
                    if (lb != sumSet.end()) {
                        ans = max(ans, s - *lb);
                    }
                    sumSet.insert(s);
                }
            }
        }
        return ans;
    }
};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

Ypuyu

如果帮助到你,可以请作者喝水~

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

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

打赏作者

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

抵扣说明:

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

余额充值