动态规划 解决二维最大子序和问题---滚动数组
Posted milaiko
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了动态规划 解决二维最大子序和问题---滚动数组相关的知识,希望对你有一定的参考价值。
动态规划之最大数组和系列
啊 ,最近实在是刷不下去动态规划了,累了累了,所以还是把之前一些比较虐我的题整理一下。
题目编号
再了解滚动数组法之前,先不做难题,先看看最基本的问题——最大子序和
最大子序和
给定一个整数数组 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[i−1]+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列。
通过将第二行和第一行累加到一个数组,然后在判断最大子矩阵按这个数组来判定,就能够得到之前行的数据。
这就算解释完滚动数组的思想了,一个数组从上往下滚动(累加),得到之前的历史记录。
那么怎么用?
- 先把滚动数组定义,并初始化
vector<int> b(m, 0); // m为列数
- 遍历矩阵,我们通过滚动数组保存了历史记录,那么遍历行的时候,需要借用双指针的想法,
一个指定的是子矩阵的最上面那行,设为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];
}
}
}
- 状态转移
这个状态转移的思想,就是和最大子序列一样
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;
}
};
以上是关于动态规划 解决二维最大子序和问题---滚动数组的主要内容,如果未能解决你的问题,请参考以下文章