1. 题目来源
相关题目:[线性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-1
,i
是从 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
的时候,就可以将n
、m
进行调换,复杂度会大大降低。
当然,本题采用 列压缩,再枚举上下边界也是可以的。这样空间复杂度就能降至 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;
}
};