参考链接
- https://leetcode-cn.com/problems/max-sum-of-rectangle-no-larger-than-k/
- https://leetcode-cn.com/problems/max-sum-of-rectangle-no-larger-than-k/solution/gong-shui-san-xie-you-hua-mei-ju-de-ji-b-dh8s/
题目描述
给你一个 m x n 的矩阵 matrix 和一个整数 k ,找出并返回矩阵内部矩形区域的不超过 k 的最大数值和。
题目数据保证总会存在一个数值和不超过 k 的矩形区域。
解题思路
前缀和
求矩阵的区域和要用到前缀和。所谓前缀和,就是矩阵某个点(x, y)与(0, 0)所确定的子矩阵的数值和,记为sum(x, y)。求前缀和的时候可以使用动态规划的思想。sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + mat[i - 1][j - 1];
暴力遍历
遍历子矩阵左上顶点和右下顶点以确定矩阵。子矩阵的和就用预先计算的前缀和来计算。但是这种方法会超时。
有序集合
暴力遍历时,有四重循环,分别是左上顶点的行号与列号、右下顶点的行号与列号。使用有序集合可以减少一层循环。具体地,使用三重循环来确定矩阵的三条边,使用有序集合查找第四条边。由于由于前缀和不具有递增性,如果先确定左边界使用二分查找法查找右边界是不行的。所以先确定右边界,并使用有序集合记录当前子矩阵的数组和。这样在寻找左边界就可以查找集合中大于 k - right 的值。
代码
暴力遍历
class Solution {
public:
int maxSumSubmatrix(vector<vector<int>>& mat, int k) {
int m = mat.size(), n = mat[0].size();
vector<vector<int>> sum(m+1, vector<int>(n+1));
for(int i = 1; i <= m; i++){
for(int j = 1; j <= n; j++){
sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + mat[i - 1][j - 1];
}
}
int ans = INT_MIN;
for(int i = 1; i <= m; i++){
for(int j = 1; j <= n; j++){
for(int p = i; p <= m; p++){
for(int q = j; q <= n; q++){
int cur = sum[p][q] - sum[i - 1][q] - sum[p][j - 1] + sum[i - 1][j - 1];
if(cur <= k){
ans = max(ans,cur);
}
}
}
}
}
return ans;
}
};
有序集合
class Solution {
public:
int maxSumSubmatrix(vector<vector<int>>& mat, int k) {
int m = mat.size(), n = mat[0].size();
vector<vector<int>> sum(m + 1,vector<int>(n + 1,0));
for(int i = 1; i <= m; i++){
for(int j = 1; j <= n; j++){
sum[i][j] = sum[i - 1][j] + sum[i][j - 1] - sum[i - 1][j - 1] + mat[i - 1][j - 1];
}
}
int ans = INT_MIN;
for(int top = 1; top <= m; top++){
for(int bot = top; bot <= m; bot++){
set<int> st;
st.insert(0);
for(int r = 1; r <= n; r++){
int right = sum[bot][r] - sum[top - 1][r];
auto left = st.lower_bound(right - k);
if(left != st.end()){
int cur = right - *left;
ans = max(ans,cur);
}
st.insert(right);
}
}
}
return ans;
}
};