目录
题十八 最大子数组和
1、算法解析
1、确定状态:
2、状态转移方程:
3、初始化:
4、填表顺序:
5、返回值:
2、代码
题十九 环形子数组的最大和
1、算法解析
1、确定状态:
2、状态转移方程:
3、初始化:
4、填表顺序:
5、返回值:
2、代码
题二十 乘积最大子数组
1、算法解析
1、确定状态:
2、状态转移方程:
3、初始化:
4、填表顺序:
5、返回值:
2、代码
题二十一 乘积为正数的最长子数组
题十八 最大子数组和
53. 最大子数组和 - 力扣(LeetCode)
1、算法解析
1、确定状态:
dp[i]位置的值,表示以i为结尾的子数组的最大和。注意,是以i为结尾,并没有规定从那个位置开始。
2、状态转移方程:
i位置有两种状态:长度为1,长度大于1。
长度为1时,以i结尾,就是i位置的值,即nums[i]
长度大于1时,以i结尾,就是要包括前面i-1位置的和,即dp[i-1] + nums[i]
3、初始化:
初始化解决的是填表越界的问题
根据状态转移方程,我们需要初始化dp[0]的位置
第一个,因为必须包含一个元素,所以只能是其本身值
即dp[0] = n[0]
4、填表顺序:
从左往右
5、返回值:
我们不知道最大值到底是以那个位置为结尾的连续子数组,因此要遍历拿出最大值。
2、代码
class Solution {
public:
int maxSubArray(vector<int>& nums) {
//1、创建dp表
int n = nums.size();
if(n==1) return nums[0];
vector<int> dp(n);
//2、初始化
dp[0] = nums[0];
//3、填表
for(int i = 1; i<n; ++i)
{
dp[i] = max(nums[i], dp[i-1] + nums[i]);
}
//4、返回值
int ret = -0x3f3f3f3f;
for(int i = 0; i<n; ++i)
{
if(dp[i] >= ret)
{
ret = dp[i];
}
}
return ret;
}
};
题十九 环形子数组的最大和
918. 环形子数组的最大和 - 力扣(LeetCode)
1、算法解析
这一题比简单的线性最大数组和多了一个环形。
我们怎么做呢?
可以不可以把环形,变成线性的数组?
如果转化为线性的数组,那就是最大连续子数组和
我们仔细研究一下这个环形,会发现:答案就只有两种可能
一个是最大连续字数组就在中间位置:
一个是最大连续子数组在两边连接处:
这第二种情况,我们会发现中间的连续子数组是最小的。
那么数组的最大值就是整个数组的和-最小连续子数组。
那么,我们就来比较这两种情况那个大即可。
1、确定状态:
我们需要两个表,一个f求最小值,一个g求最大值:
f[i]位置的值,表示以i为结尾的最小连续子数组和。注意,是以i为结尾,并没有规定从那个位置开始。
g[i]位置的值,表示以i为结尾的最大连续子数组和。注意,是以i为结尾,并没有规定从那个位置开始。
2、状态转移方程:
i位置有两种状态:长度为1,长度大于1。
长度为1时,以i结尾,就是i位置的值,即nums[i]
长度大于1时,以i结尾,就是要包括前面i-1位置的和,即f[i-1] + nums[i]
f[i] = min(nums[i], f[i-1] + nums[i]);
g[i] = max(nums[i], g[i-1] + nums[i]);
一个求最大,一个求最小。
3、初始化:
初始化解决的是填表越界的问题
根据状态转移方程,我们需要初始化f[0]的位置
f[0] = nums[0]
g[0] = nums[0]
4、填表顺序:
从左往右
5、返回值:
找出最大值,和sum-最小值对比,返回大者。
但是,需要注意,如果所有的数组都是负数,最小值就是所有数字的和。
sum - min = 0;就会返回0
但是返回值不能是0,因为必须包含一个元素。
所以需要特别判断
2、代码
class Solution {
public:
int maxSubarraySumCircular(vector<int>& nums) {
//1、创建dp表
int n = nums.size();
if(n == 1) return nums[0];
vector<int> f(n);
auto g = f;
int sum = 0;
for(auto e : nums)
{
sum += e;
}
//2、初始化
f[0] = nums[0];
g[0] = nums[0];
//3、填表
for(int i = 1; i<n; ++i)
{
f[i] = min(nums[i], f[i-1] + nums[i]);//找最小值
g[i] = max(nums[i], g[i-1] + nums[i]);//找最大值
}
//4、返回值
int max_value = -0x3f3f3f3f;
int min_value = 0x3f3f3f3f;
for(int i= 0; i<n; ++i)
{
if(g[i] >= max_value)
max_value = g[i];
if(f[i] <= min_value)
min_value = f[i];
}
if(sum == min_value)
return max_value;
return max(max_value, sum - min_value);
}
};
题二十 乘积最大子数组
152. 乘积最大子数组 - 力扣(LeetCode)
1、算法解析
1、确定状态:
也是一个连续子数组求最大最小的问题。
我们创建一个一维dp表。
dp[i]的值,表示以i为结尾的最大乘积子数组,注意,并没有规定是从那个位置作为开始位置。
2、状态转移方程:
i位置有两种状态:长度为1,长度大于1。
长度为1时,以i结尾,就是i位置的值,即nums[i]
长度大于1时,以i结尾,就是要包括前面i-1位置的和,即dp[i-1] * nums[i]
按照原来的逻辑,上述的推导是没有问题的。
但是,如果nums[i]是一个负数,dp[i-1]是一个最大值,那么最大值×负数,直接变成最小值。
出问题了。
正确的逻辑应该是:
如果nums[i]是一个正数,那么dp[i-1]的值,应该是最大值
如果nums[i]是一个负数,那么dp[i-1]的值,应该是最小值。
所以,按照我们的逻辑推导,我们需要两个状态表:
f[i]代表表示以i为结尾的最大乘积子数组
g[i]代表表示以i为结尾的最小乘积子数组
如图:
3、初始化:
初始化解决的是填表越界的问题
根据状态转移方程,我们需要初始化f[0]的位置
f[0] = g[0] = nums[0];
4、填表顺序:
从左往右
5、返回值:
返回f表中的最大值。
2、代码
class Solution {
public:
int maxProduct(vector<int>& nums) {
//1、创建dp表
int n = nums.size();
if(n == 1) return nums[0];
vector<int > f(n);
auto g =f;
//2、初始化
f[0] = g[0] = nums[0];
//f[i]为最小值
//g[i]为最大值
//3、填表
for(int i = 1; i<n; ++i)
{
f[i] = min(nums[i], min(g[i-1] * nums[i], f[i-1] * nums[i]));
g[i] = max(nums[i], max(f[i-1] * nums[i], g[i-1] * nums[i]));
}
//4、返回值
int ret = INT_MIN;
for(int i = 0; i<n; ++i)
{
if(g[i] >= ret)
ret = g[i];
}
return ret;
}
};
题二十一 乘积为正数的最长子数组
152. 乘积最大子数组 - 力扣(LeetCode)