题目描述:
给定一个正整数、负整数和 0 组成的 N × M 矩阵,编写代码找出元素总和最大的子矩阵。
返回一个数组 [r1, c1, r2, c2],其中 r1, c1 分别代表子矩阵左上角的行号和列号,r2, c2 分别代表右下角的行号和列号。若有多个满足条件的子矩阵,返回任意一个均可。
输入:
[
[-1,0],
[0,-1]
]
输出:[0,1,0,1]
解释:最大的和是0 答案可以是0 1 0 1或者1 0 1 0
说明:1 <= matrix.length, matrix[0].length <= 200
链接:https://leetcode-cn.com/problems/max-submatrix-lcci
暴力法:
枚举矩阵的两个坐标 (x1,y1) (x2,y2),也就是4层for循环。
利用二维前缀和数组可以在已知两个坐标的前提下O(1)求出和,所以总的复杂度是O(n^4)。
代码:
class Solution {
public:
vector<int> getMaxMatrix(vector<vector<int>>& matrix) {
int n=matrix.size();
int m=matrix[0].size();
vector<vector<int> > sum(n,vector<int>(m,0)); //n行m列的sum初始化为0
sum[0][0]=matrix[0][0];
for (int i=1; i<n; i++) sum[i][0]=matrix[i][0]+sum[i-1][0];
for (int j=1; j<m; j++) sum[0][j]=matrix[0][j]+sum[0][j-1];
for (int i=1; i<n; i++)
for (int j=1; j<m; j++)
sum[i][j]=matrix[i][j]+sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
/*for (int i=0; i<n; i++){
for (int j=0; j<m; j++) cout<<sum[i][j]<<" ";
cout<<endl;
}*/
vector<int> a(4,0);
int ans=-1000000000;
for (int x1=0; x1<n; x1++){
for (int y1=0; y1<m; y1++){
for (int x2=x1; x2<n; x2++){
for (int y2=y1; y2<m; y2++){
int t;
if (x1==0 && y1==0)
t=sum[x2][y2];
else if (x1==0)
t=sum[x2][y2]-sum[x2][y1-1];
else if (y1==0)
t=sum[x2][y2]-sum[x1-1][y2];
else t=sum[x2][y2]-sum[x1-1][y2]-sum[x2][y1-1]+sum[x1-1][y1-1];
//cout<<t<<endl;
if (t>ans){
ans=t;
while (a.size()!=0) a.pop_back();
a.push_back(x1); a.push_back(y1);
a.push_back(x2); a.push_back(y2);
}
}
}
}
}
return a;
}
};
求二维前缀和矩阵sum时的状态转移方程是:sum[i][j]=matrix[i][j]+sum[i-1][j]+sum[i][j-1]-sum[i-1][j-1];
已知两个坐标 (x1,y1) (x2,y2),求其矩阵和t时:t=sum[x2][y2]-sum[x1-1][y2]-sum[x2][y1-1]+sum[x1-1][y1-1];
这其中有-1的存在,所以当x1,y1,x2,y2是从0枚举时会运行时错误,需要单独拿出来判断下。
最终写成的代码比较好理解,但是有点儿繁琐,而且超时是一定的。
二维矩阵转一维:
一维的最大子序列和我们都很熟悉,不管是用动态规划还是贪心,都能在O(n) 的时间复杂度内,求出最大连续子序列的和。
不了解的可以看:三种解法求:最大连续子序列的和
假设我们固定了矩阵的上边和下边,再把其中每一列的数看做一个整体(因为选就要全选,或者全不选,本身也是一个整体),这样二维的矩阵和就变成了一维的最大子序列和。
数组b的作用是在矩阵的上下边确定的时候,也就是i,j确定的时候,求出包含在中间的每一列的总和。
然后再用这些总和,也就是b数组直接求最大子序列。
代码:
class Solution {
public:
vector<int> getMaxMatrix(vector<vector<int>>& matrix) {
int n=matrix.size();
int m=matrix[0].size();
vector<int> a(4,0);
int b[m];
int ans=-10000000000; //所求的最大和
for (int i=0; i<n; i++){
memset(b,0,sizeof(b));
for (int j=i; j<n; j++){
for (int k=0; k<m; k++) b[k]+=matrix[j][k];
int sum=0; //当前统计的和
int start=0; //矩阵起点
for (int k=0; k<m; k++){ //最大连续子序列的和
if (sum>=0) sum+=b[k];
else{
sum=b[k];
start=k;
}
if (sum>ans){
ans=sum;
while (a.size()!=0) a.pop_back();
a.push_back(i); a.push_back(start);
a.push_back(j); a.push_back(k);
}
}
}
}
return a;
}
};
其中,因为矩阵的上边界 i 确定之后,然后下边界递增,所以这个矩阵每列的和只需要加上新加的那一行的元素就可以了。
在每次上边界 i 改变的时候,再清空 b,重新计算。
时间复杂度:O(n^3)
总结:
正解的二维转一维,代码含金量更高,但是写起来确实比暴力要简洁。