动态规划 解决二维最大子序和问题---滚动数组

Posted milaiko

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态规划 解决二维最大子序和问题---滚动数组相关的知识,希望对你有一定的参考价值。

动态规划之最大数组和系列

啊 ,最近实在是刷不下去动态规划了,累了累了,所以还是把之前一些比较虐我的题整理一下。

题目编号

53. 最大子序和

最大子矩阵

矩阵区域不超过k的最大数组和

再了解滚动数组法之前,先不做难题,先看看最基本的问题——最大子序和

最大子序和

给定一个整数数组 nums ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例

输入:nums = [-2,1,-3,4,-1,2,1,-5,4]
输出:6
解释:连续子数组 [4,-1,2,1] 的和最大,为 6 。

这道题很简单,设定一个变量sum,遍历nums,当sum小于0时,便舍弃掉,将sum设为nums[i]。

动态方程就是

dp = max(sum, dp);

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
    // 设定初始值, max使用来存储sum的最大值,这就是我们要求的最大和,sum是遍历nums的和
        int n = nums.size();
        int sum = nums[0];
        int maxm = nums[0];
        // 在这个遍历中,当sum小于0时,nums[i]加上它只会变小,于是直接舍弃掉,sum = nums[i]
        //如果sum > 0, 那么sum继续累加,其中不要注意更改最大和maxm
        for(int i=1;i<n;i++){
            if(sum < 0){
                sum = nums[i];
                maxm = max(sum, maxm);
            }
            else{
                sum += nums[i];
                maxm = max(sum, maxm);
            }
        }
        return maxm;
    }
};

接下来才是重点,因为涉及到了二维,大家都应该会一脸懵(大佬除外)

最大子矩阵

给定一个正整数、负整数和 0 组成的 N × M 矩阵,编写代码找出元素总和最大的子矩阵。

返回一个数组 [r1, c1, r2, c2],其中 r1, c1 分别代表子矩阵左上角的行号和列号,r2, c2 分别代表右下角的行号和列号。若有多个满足条件的子矩阵,返回任意一个均可。

注意:本题相对书上原题稍作改动

示例:

输入:
[
   [-1,0],
   [0,-1]
]
输出:[0,1,0,1]
解释:输入中标粗的元素即为输出所表示的矩阵

先来分析一下题意

  • 找到元素总和最大的子矩阵——也就是说我们要转移到的最终状态为子矩阵之和最大
  • 返回左上角和右下角的行号和列号

诶,和最大子序和是不是很像,都是要求最大和,只不过一个是一维,一个是二维(一个是简单题,一个是难题)

为了保持头脑清醒,再次强调一下,挡住我们的两座大山

  • 怎么进行状态转移
    • 而该问题又可以说, 怎么知道哪个子矩阵最大?
  • 怎么存储左上角和右下角的坐标。

再告诉你们正解之前,我想按我一开始的思路去解释我这个思路为何不行(如果想不听我废话的同学可以直接跳)

一开始的想法 —— 按顺序遍历

这道题和最大子序和的思想是一致的。

我一开始想按顺序遍历,就是把这个二维当作一维的处理。这样做等于把n行的东西变为一行。但是,这样做有用吗?

for(int i=0;i<row;i++){
    for(int j=0;j<colum;j++){
        .....
    }
}

现在有这个矩阵

[
    [-1,0,2]
    [ 2,1,3]
    [2,2,3]
]

按最大子序和的套路去做。

因为第一个是-1,很自然地舍弃掉这个数字,从第一行第二列的0开始,按照 d p [ i ] = m a x ( d p [ i − 1 ] + n u m s [ i ] , n u m s [ i ] ) dp[i] = max(dp[i-1]+nums[i], nums[i]) dp[i]=max(dp[i1]+nums[i],nums[i])的方程,会一直到最后一个数字,那么左上角的最优选择是 [ 0 , 1 ] [0,1] [0,1]?,显然不是,因为第一列还有正数,这么做会舍弃掉第一列的数字。所以完全不能使用这种遍历使得二维变成一维。

那么到底怎样才能使得子矩阵最大?也可以说怎么去计算子矩阵的和?

其实从刚刚的分析,子矩阵的必须从最上行到最下行然后按列累加。

正解

[
    [-1,0,2]
    [ 2,1,3]
    [2,2,3]
]

为了能够在遍历第二行的时候,还能直到第一行的信息,我们可以定义一个数组 b [ m ] , m b[m],m b[m],m为列数。

当遍历到第二行时, b [ i ] = r o w 2 [ i ] + r o w 1 [ i ] b[i] = row2[i] + row1[i] b[i]=row2[i]+row1[i]

row2第二行,row1第一行,i指第i列。

通过将第二行和第一行累加到一个数组,然后在判断最大子矩阵按这个数组来判定,就能够得到之前行的数据。

这就算解释完滚动数组的思想了,一个数组从上往下滚动(累加),得到之前的历史记录。

那么怎么用?

  1. 先把滚动数组定义,并初始化
vector<int> b(m, 0); // m为列数
  1. 遍历矩阵,我们通过滚动数组保存了历史记录,那么遍历行的时候,需要借用双指针的想法,

一个指定的是子矩阵的最上面那行,设为i行, 一个指定的是子矩阵的最下面的那行,设为j行。

并且在更新最上行,也可以说是在更新确定子矩阵的范围时,需要重新将b归为0。 并在遍历的时候将滚动数组更新。

int sum=0; // sum为子矩阵的和
for(int i=0;i<n;i++){
    for(int t=0;t<m;t++){ //将滚动数组重新初始化
        b[t] =0;
    }
    for(int j=i;j<n;j++){
        sum =0;
        for(int k=0;k<m;k++){
            b[k] += matrix[j][k];
        }
    }
}
  1. 状态转移

这个状态转移的思想,就是和最大子序列一样

if(sum > 0){
    sum += b[k];
}
else{
    sum = b[k];
    left_col = k;
    left_row = i;
}
if(sum > maxsum){
    maxsum = sum;
    ans[0] = left_row;
    ans[1] = left_colum;
    ans[2] = j;
    ans[3] = k;
}

全代码

// 最大子矩阵
#include<iostream>
#include<vector>
#include<algorithm>
#include<bits/stdc++.h>  //INT_MIN,INT_MAX
using namespace std;
class Solution {
public:
    vector<int> getMaxMatrix(vector<vector<int>>& matrix) {
        int n =matrix.size(),m = matrix[0].size();
        vector<int> ans(4);     //用来存储左上角和右上角的坐标
        int Maxsum = INT_MIN;
        vector<int> b(m, 0);
        int sum;
        int left_col, left_row; //用来存储左上角
        for(int i=0;i<n;i++){
            for(int t=0;t<m;t++){
                b[t] =0;
            }
            for(int j=i;j<n;j++){
                sum =0;
                for(int k=0;k<m;k++){
                    b[k] += matrix[j][k];
                    if(sum > 0){
                        sum += b[k];
                    }
                    else{
                        sum = b[k];
                        left_col = k;
                        left_row = i;
                    }
                    if(sum > Maxsum){
                        Maxsum = sum;
                        ans[0] = left_row;
                        ans[1] = left_col;
                        ans[2] = j;
                        ans[3] = k;
                    }
                }
            }
        }
        return ans;
    }
};

矩阵区域不超过k的最大数组和

给你一个 m x n 的矩阵 matrix 和一个整数 k ,找出并返回矩阵内部矩形区域的不超过 k 的最大数值和。

题目数据保证总会存在一个数值和不超过 k 的矩形区域。

示例一:

输入:matrix = [[1,0,1],[0,-2,3]], k = 2
输出:2
解释:蓝色边框圈出来的矩形区域 [[0, 1], [-2, 3]] 的数值和是 2,且 2 是不超过 k 的最大数字(k = 2)。

分析

同样是使用滚动数组法解决这道题。只不过这里限定了一个条件,要求数值和不超过k并且子矩阵的数值和还得是最大。并且求的是数值和,而不是左右下标。

这道题主要把握三个要点

  • colsum 滚动数组。 colsum表示以l、r为左右边界,任意矩形的面积的和。
  • dp状态转移方程

在符合小于k的条件下, m a x m = m a x ( m a x m , d p m a x ( c o l s u m , k ) ) maxm = max(maxm, dpmax(colsum ,k)) maxm=max(maxm,dpmax(colsum,k)),注意colsum满足小于k。

  • 最大子序和思想

当colsum小于0的时候, c o l s u m = a r r [ i ] colsum = arr[i] colsum=arr[i],舍弃掉原来的colsum,

这次采用从左滚到右的方法去使用滚动数组
下面

public:
    int dpmax(vector<int>& arr, int k) {
        int colSum = arr[0], colMax = colSum;
        // O(rows)
        for (int i = 1; i < arr.size(); i++) {
            if (colSum > 0) colSum += arr[i];
            else colSum = arr[i];
            if (colSum > colMax) colMax = colSum;
        }
        if (colMax <= k) return colMax;
        // O(rows ^ 2)
        int max = INT_MIN;
        for (int l = 0; l < arr.size(); l++) {
            int sum = 0;
            for (int r = l; r < arr.size(); r++) {
                sum += arr[r];
                if (sum > max && sum <= k) max = sum;
                if (max == k) return k; // 尽量提前
            }
        }
        return max;
    }


    int maxSumSubmatrix(vector<vector<int>>& matrix, int k){
        int n = matrix.size();int m = matrix[0].size();         //n是行数,m是列数
        int maxm = INT_MIN;
        for(int l=0;l<m;l++){
            vector<int> colsum(n,0);
            for(int r=l;r<m;r++){
                for(int j=0;j<n;j++){
                    colsum[j] += matrix[j][r];
                }
                maxm = max(maxm, dpmax(colsum,k));
            }
        }
        return maxm;
    }
};

以上是关于动态规划 解决二维最大子序和问题---滚动数组的主要内容,如果未能解决你的问题,请参考以下文章

486,动态规划解最大子序和

用动态规划算法求最大子序和

leetcode 最大子序和 动态规划

53. 最大子序和-动态规划

leetcode-最大子序和(动态规划讲解)

最大子序和 --动态规划