目录
一、最大子数组和
1、题目解析
题目的意思就是在一个数组里找一个和为最大值的子数组,这个子数组第一个和最后一个元素的位置不确定,但是只需要返回最大值即可
2、算法原理
a状态表示方程
找到状态表达方程的小技巧:经验+题目要求
比如:dp[i]表示+巴拉巴拉
比如本题就是:dp[i]表示以i节点为结尾时可以得到的最大子数组和
b状态转移方程
想要找到状态转移方程,也有一个小技巧:根据最近的一步划分问题
就比如这个图中,i节点表示以i节点为结尾时最大的子数组和,i-1则表示以i-1节点为结尾时最大的子数组和。
那么我们就需要考虑dp[i]如何通过dp[i-1]得到,有两种情况:
dp[i]=max(dp[i-1]+nums[i],nums[i])
有些同学会有点疑惑,为什么dp[i-1]+nums[i]小于nums[i]的时候dp[i]的值是nums[i],因为我们的定义就是,以i为尾节点的时候,所以这个值无论如何都是要包括i节点的
c初始化
由于本题中出现了i-1,所以我们初始化需要考虑到 第一个节点时的边界问题
这道题我们通过设置一个节点在前面,为了不影响dp[1]的值,将其值设置为0,
这样我们的程序从1开始进入循环就不会出现越界问题了。
这种方法我称之为虚拟节点的初始化,不过这种方法有两点需要注意的点:
1、给虚拟节点设置的初始值需要考虑到后续填表时取值的正确性
2、需要注意下标映射,因为我们的dp表比nums多了一个虚拟节点,我们对nums取值时需要下标-1
d填表顺序
从左到右
e返回值
在dp表中取出一个最大值
3、代码编写
class Solution {
public:
int maxSubArray(vector<int>& nums) {
//建表
//初始化
//填表
//返回值
int n=nums.size();
vector<int> dp(n+1,0);
for(int i=1;i<=n;i++){
dp[i]=max(dp[i-1]+nums[i-1],nums[i-1]);
}
int Max=INT_MIN;
for(int i=1;i<=n;i++){
Max=max(dp[i],Max);
}
return Max;
}
};
我记得之前在 leetcode 的题解上,看到⼀句⾮常骚的解释:⾛完这⼀⽣:如果我和你在⼀起会变得更好,那我们就在⼀起,否则我就丢下你。我回顾我最光辉的时刻就是和不同⼈在⼀起,变得更好的最⻓连续时刻。
二、环形子数组的最大和
1、题目描述
2、算法原理
a状态表示方程
求子数组和的时候,会有两种情况:
1、子数组为中间的数组,这就和第一道一样的方法处理
2、子数组在头部和尾部
求出这种情况的最大子数组和就需要将整个数组的和sum减去中间子数组最小和即可,也就是sum-min
这样就将问题转换成我们熟悉的问题了。
状态表示方程:
f[i]表示以i节点为结尾时,最大子数组和
g[i]表示以i节点为结尾时,最小子数组和
b状态转移方程
根据最近的一步划分问题。
c初始化
d填表顺序
从左到右
e返回值
只需要将f的最大值和sum-g的最小值相比即可。
但是有一个特殊情况需要考虑,如果数组里面全是负数,我们只需要将最大的负数返回即可可。
3、代码编写
class Solution {
public:
int maxSubarraySumCircular(vector<int>& nums) {
int n=nums.size();
vector<int> f(n+1,0);
vector<int> g(n+1,0);
int sum=0;
for(int i=0;i<n;i++){
sum+=nums[i];
}
for(int i=1;i<=n;i++){
f[i]=max(f[i-1]+nums[i-1],nums[i-1]);
g[i]=min(g[i-1]+nums[i-1],nums[i-1]);
}
int Min=INT_MAX;
int Max=INT_MIN;
for(int i=1;i<=n;i++)
{
Min=min(g[i],Min);
Max=max(f[i],Max);
}
return sum==Min?Max:max(sum-Min,Max);
}
};
三、乘积为正数的最长子数组长度
1、题目描述
2、算法原理
a状态表示方程
小技巧:经验+题目要求
状态表⽰: f [i] 表⽰「所有以 i 结尾的⼦数组,乘积为正数的最⻓⼦数组的⻓度」。思考状态转移:对于 i 位置上的 nums[i] ,我们可以分三种情况讨论:i. 如果 nums[i] = 0 ,那么所有以 i 为结尾的⼦数组的乘积都不可能是正数,此时dp[i] = 0 ;ii. 如果 nums[i] > 0 ,那么直接找到 f [i - 1] 的值(这⾥请再读⼀遍 f [i - 1] 代表的意义,并且考虑如果 f [i - 1] 的结值是 0 的话,影不影响结果),然后加⼀即可,此时 f [i] = f[i - 1] + 1 ;iii. 如果 nums[i] < 0 ,这时候你该蛋疼了,因为在现有的条件下,你根本没办法得到此时的最⻓⻓度。因为乘法是存在「负负得正」的,单单靠⼀个 f[i - 1] ,我们⽆法推导出 f[i] 的值。但是,如果我们知道以 i - 1 为结尾的所有⼦数组,乘积为负数的最⻓⼦数组的⻓度g[i - 1] ,那么此时的 f [i] 是不是就等于 g[i - 1] + 1 呢?通过上⾯的分析,我们可以得出,需要两个 dp 表,才能推导出最终的结果。不仅需要⼀个乘积 为正数的最⻓⼦数组,还需要⼀个乘积为负数的最⻓⼦数组。
b状态转移方程
遍历每⼀个位置的时候,我们要同步更新两个 dp 数组的值。对于 f[i] ,也就是以 i 为结尾的乘积为「正数」的最⻓⼦数组,根据 nums[i] 的值,可以分为三种情况:i. nums[i] = 0 时,所有以 i 为结尾的⼦数组的乘积都不可能是正数,此时 f[i] =0 ;ii. nums[i] > 0 时,那么直接找到 f[i - 1] 的值(这⾥请再读⼀遍 f[i - 1] 代表的意义,并且考虑如果 f[i - 1] 的结值是 0 的话,影不影响结果),然后加⼀即可,此时 f[i] = f[i - 1] + 1 ;iii. nums[i] < 0 时,此时我们要看 g[i - 1] 的值(这⾥请再读⼀遍 g[i - 1] 代表的意义。因为负负得正,如果我们知道以 i - 1 为结尾的乘积为负数的最⻓⼦数组的⻓度,加上 1 即可),根据 g[i - 1] 的值,⼜要分两种情况:1. g[i - 1] = 0 ,说明以 i - 1 为结尾的乘积为负数的最⻓⼦数组是不存在的,⼜因为 nums[i] < 0 ,所以以 i 结尾的乘积为正数的最⻓⼦数组也是不存在的,此时 f[i] = 0 ;2. g[i - 1] != 0 ,说明以 i - 1 为结尾的乘积为负数的最⻓⼦数组是存在的,⼜因为 nums[i] < 0 ,所以以 i 结尾的乘积为正数的最⻓⼦数组就等于 g[i - 1] + 1 ;综上所述, nums[i] < 0 时, f[i] = g[i - 1] == 0 ? 0 : g[i - 1] +1;对于 g[i] ,也就是以 i 为结尾的乘积为「负数」的最⻓⼦数组,根据 nums[i] 的值,可以分为三种情况:i. nums[i] = 0 时,所有以 i 为结尾的⼦数组的乘积都不可能是负数,此时 g[i] = 0 ;ii. nums[i] < 0 时,那么直接找到 f[i - 1] 的值(这⾥请再读⼀遍 f[i - 1] 代表的意义,并且考虑如果 f[i - 1] 的结值是 0 的话,影不影响结果),然后加⼀即可 (因为正数 * 负数 = 负数),此时 g[i] = f[i - 1] + 1 ;iii. nums[i] > 0 时,此时我们要看 g[i - 1] 的值(这⾥请再读⼀遍 g[i - 1] 代表的意义。因为正数 * 负数 = 负数),根据 g[i - 1] 的值,⼜要分两种情况:1. g[i - 1] = 0 ,说明以 i - 1 为结尾的乘积为负数的最⻓⼦数组是不存在的,⼜因为 nums[i] > 0 ,所以以 i 结尾的乘积为负数的最⻓⼦数组也是不存在的,此时 f[i] = 0 ;2. g[i - 1] != 0 ,说明以 i - 1 为结尾的乘积为负数的最⻓⼦数组是存在的,⼜因为 nums[i] > 0 ,所以以 i 结尾的乘积为正数的最⻓⼦数组就等于 g[i -1] + 1 ;综上所述, nums[i] > 0 时, g[i] = g[i - 1] == 0 ? 0 : g[i - 1] +1 ;这⾥的推导⽐较绕,因为不断的出现「正数和负数」的分情况讨论,我们只需根据下⾯的规则,严格找到此状态下需要的 dp 数组即可:i. 正数 * 正数 = 正数ii. 负数 * 负数 = 正数iii. 负数 * 正数 = 正数 * 负数 = 负数
c初始化
为了防止i-1时下标越界:
我们通过虚拟节点进行初始化,给第一个节点设置的值为0 即可。
d填表顺序
从左到右,两个表一起填写
e返回值
返回f中最大值即可
3、代码编写
class Solution {
public:
int getMaxLen(vector<int>& nums) {
int n =nums.size();
vector<int> f(n+1,0);
vector<int> g(n+1,0);//以i为尾相乘为负数的最长位数
for(int i=1;i<=n;i++){
if(nums[i-1]>0){
f[i]=f[i-1]+1;
g[i]=g[i-1]==0?0:g[i-1]+1;
}else if(nums[i-1]<0){
f[i]=g[i-1]==0?0:g[i-1]+1;
g[i]=f[i-1]+1;
}
else{
f[i]=0;
g[i]=0;
}
}
int Max=INT_MIN;
for(int i=1;i<=n;i++){
Max=max(f[i],Max);
}
return Max;
}
};