一、矩形区域不超过 K 的最大数值和
给定一个非空二维矩阵 matrix 和一个整数 k,找到这个矩阵内部不大于 k 的最大矩形和。
输入: matrix = [[1,0,1],[0,-2,3]], k = 2
输出: 2
解释: 矩形区域 [[0, 1], [-2, 3]] 的数值和是 2,且 2 是不超过 k 的最大数字(k = 2)。
说明:
矩阵内的矩形区域面积必须大于 0。
如果行数远大于列数,你将如何解答呢?
方法一:裸前缀和
思路
…
class Solution {
public:
int maxSumSubmatrix(vector<vector<int>>& g, int k) {
int n = g.size(), m = g[0].size(), ans = INT_MIN, s[n+1][m+1]; memset(s, 0, sizeof s);
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] + g[i-1][j-1];
}
for (int x1 = 0; x1 <= n; x1++)
for (int y1 = 0; y1 <= m; y1++)
for (int x2=x1+1; x2 <= n; x2++)
for (int y2=y1+1; y2 <= m; y2++) {
int sum = s[x2][y2] - s[x1][y2] - s[x2][y1] + s[x1][y1];
if (sum <= k && sum > ans) ans = sum;
}
return ans;
}
};
不太理想的一次 ac
执行用时:1068 ms
内存消耗:8.5 MB
复杂度分析
- 时间复杂度: O ( n 4 ) O(n^4) O(n4),
- 空间复杂度: O ( n 2 ) O(n^2) O(n2),
方法二:固定左右边界 + 二分
Q:如果行数远大于列数,你将如何解答呢?
别人的优秀思路:固定两个左右边界(矩阵的左右边界),然后从小到大枚举行,在枚举行的同时,用 set 存储每一行得到的前缀和的累加值 tot
下一次枚举时,假如当前行的前缀和值为 sum,总共为 tot,我们只需在 set 中二分找到一个值 v ( 满 足 v ⩾ t o t − k , 即 t o t − v ⩽ k ) v(满足\ v \geqslant tot-k,即\ tot-v \leqslant k) v(满足 v⩾tot−k,即 tot−v⩽k)
class Solution {
public int maxSumSubmatrix(int[][] g, int k) {
int n = g.length, m = g[0].length, ans = Integer.MIN_VALUE, s[][] = new int[n+1][m+1];
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++) {
s[i][j] = s[i][j-1] + g[i-1][j-1];
}
for (int l = 0; l <= m; l++)
for (int r = l+1; r <= m; r++) {
TreeSet<Integer> st = new TreeSet<>(); st.add(0);
int tot = 0;
for (int i = 1; i <= n; i++) {
tot += s[i][r]-s[i][l];
Integer v = st.ceiling(tot-k);
if (v != null)
ans = Math.max(ans, tot-v);
st.add(tot);
}
}
return ans;
}
}
ceiling 方法对应 C++ set 的 lower_bound
复杂度分析
- 时间复杂度: O ( m 2 × n × l o g n ) O(m^2 × n × logn) O(m2×n×logn),
- 空间复杂度: O ( n m ) O(nm) O(nm),
二、最大子矩阵
给定一个正整数和负整数组成的 N × M 矩阵,编写代码找出元素总和最大的子矩阵。
返回一个数组 [r1, c1, r2, c2],其中 r1, c1 分别代表子矩阵左上角的行号和列号,r2, c2 分别代表右下角的行号和列号。若有多个满足条件的子矩阵,返回任意一个均可。
注意:本题相对书上原题稍作改动
输入:
[
[-1,0],
[0,-1]
]
输出: [0,1,0,1]
解释: 输入中标粗的元素即为输出所表示的矩阵
说明:
1 <= matrix.length, matrix[0].length <= 200
方法一:逻辑优化
n 2 × m 2 n^2 × m^2 n2×m2 暴力不难想…
思路
该优化解法比较有趣,利用的是最大字段和思想:当前面的 A [ i . . . j ] A[i...j] A[i...j] 累加和 s < 0 s < 0 s<0 时,我们应该舍弃这一段前缀和,从 j + 1 j+1 j+1 位置重新开始统计
算法
- 固定矩阵的首列、尾列下标 l , r l,r l,r,然后枚举行 i i i
- 在枚举的时候,如果发现前缀和 s < 0 s < 0 s<0 的话,此时应该更新 s i si si,也就是对应上述的思想( s i si si 子矩阵的首行下标、 i i i 为尾行下标)
- 如果此时的前缀和 s 大于历史最大值 mx,应该更新
vector<int> ans
这样我只需要 O ( m 2 × n ) O(m^2 × n) O(m2×n)
class Solution {
public:
vector<int> getMaxMatrix(vector<vector<int>>& g) {
int n = g.size(), m = g[0].size(), mx = INT_MIN, s[n+1][m+1]; memset(s, 0, sizeof s);
for (int i = 1; i <= n; i++)
for (int j = 1; j <= m; j++)
s[i][j] = s[i][j-1] + g[i-1][j-1];
vector<int> ans;
for (int l = 0; l < m; l++)
for (int r=l+1; r <= m; r++) {
int cur = 0, si = 1;
for (int i = 1; i <= n; i++) {
if (cur < 0) {
cur = s[i][r]-s[i][l], si = i;
} else {
cur += s[i][r]-s[i][l];
}
if (cur > mx)
mx = cur, ans = {si-1, l, i-1, r-1};
}
}
return ans;
}
};
复杂度分析
- 时间复杂度: O ( m 2 × n ) O(m^2 × n) O(m2×n),
- 空间复杂度: O ( m n ) O(mn) O(mn)