最大子矩阵和 (LeetCode面试题17.24)

题目描述:

给定一个正整数、负整数和 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数组直接求最大子序列。

捕获.JPG

代码:

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)

总结:

正解的二维转一维,代码含金量更高,但是写起来确实比暴力要简洁。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值