一、题目
二、题解
1、动态规划解题步骤:
- 1)重述问题
- 2)找到最后一步
- 3)去掉最后一步,问题变成了什么?(原问题答案如何从去掉最后一步推出)
- 4)考虑边界
2、结合题目具体分析:
假设选择了
4
4
4这个数,作为最后一个数,那么此时原问题答案为:
三、DFS实现
//1、选择一个连续子数组,让其区间和最大
//2、选择了一个数,作为子数组的最后一个数
//3、选择一个数(4之前的数)作为数组中的最后一个数,使得其区间和最大
//答案=选择了之前的某个数的区间和+4
//4、边界->数组结束
1、思路解析:
1)遍历数组,选择一个数 x x x作为区间和的最后一个数,并调用递归函数
int res=-1e9;
int n=nums.size();
for(int i=0;i<n;i++) //枚举最后一个数
{
res=max(res,dfs(nums,i));
}
2)在选择了 x x x的情况下,往前递归,如果不能往下的话,最大值就应该是它自己
3)边界处理:当遍历完数组,return 0
,表示此时递归函数的值为0,随后逐层归
int dfs(vector<int>& nums,int x)
{
if(x<0) return 0;
int ans=-1e9;
//选择x之前的数作为其最后一个数
//如果不能往下的话,最大值应该是它自己
ans=max(nums[x],dfs(nums,x-1)+nums[x]);
return ans;
}
2、递归函数解析:
1)在第16行的代码中,这样写是为了实现动态规划中的“选择当前数是否能继续延伸成一个更大的子数组”:
ans=max(nums[x], dfs(nums,x-1)+nums[x]);
解释:
-
nums[x]
:表示如果只选择当前元素nums[x]
,那么当前子数组的和就是它本身。
-
dfs(nums, x-1) + nums[x]
:表示如果之前的子数组(由x-1
索引开始的子数组)能够和当前的nums[x]
连接起来形成一个更大的子数组,那么就将这个更大的和计算出来。
2)为什么不写成 :ans=max(ans, dfs(nums, x-1) + nums[x]);
是因为在递归的过程中,我们不仅要考虑从前一个元素 x-1
加上当前的元素 nums[x]
,还需要考虑一个边界情况,即当前元素单独作为一个子数组时,它的最大和就是 nums[x]
本身。因此,我们需要对比这两种情况:
nums[x]
:当前元素本身作为一个新的子数组;dfs(nums, x-1) + nums[x]
:将当前元素加到前一个子数组的和上。
因此,应该用 max(nums[x], dfs(nums, x-1) + nums[x])
来选择这两种情况中的最大值。
2、代码解析:
//1、选择一个连续子数组,让其区间和最大
//2、选择了一个数,作为子数组的最后一个数
//3、选择一个数(4之前的数)作为数组中的最后一个数,使得其区间和最大
//答案=选择了之前的某个数的区间和+4
//4、边界->数组结束
class Solution {
public:
int dfs(vector<int>& nums,int x)
{
if(x<0) return 0;
int ans=-1e9;
//选择x之前的数作为其最后一个数
//如果不能往下的话,最大值应该是它自己
ans=max(nums[x],dfs(nums,x-1)+nums[x]);
return ans;
}
int maxSubArray(vector<int>& nums)
{
int res=-1e9;
int n=nums.size();
for(int i=0;i<n;i++) //枚举最后一个数
{
res=max(res,dfs(nums,i));
}
return res;
}
};
四、记忆化搜索
1、思路解析
1)在DFS(递归)中,存在着大量的重复计算,因此我们可以使用记忆化搜索来进行优化
2、代码解析:
//1、选择一个连续子数组,让其区间和最大
//2、选择了一个数,作为子数组的最后一个数
//3、选择一个数(4之前的数)作为数组中的最后一个数,使得其区间和最大
//答案=选择了之前的某个数的区间和+4
//4、边界->数组结束
class Solution {
public:
int mem[200007];
int dfs(vector<int>& nums,int x)
{
if(x<0) return 0;
int ans=-1e9;
//选择x之前的数作为其最后一个数
//如果不能往下的话,最大值应该是它自己
ans=max(nums[x],dfs(nums,x-1)+nums[x]);
mem[x]=ans;
return mem[x];
}
int maxSubArray(vector<int>& nums)
{
int res=-1e9;
int n=nums.size();
for(int i=0;i<n;i++) //枚举最后一个数
{
res=max(res,dfs(nums,i));
}
return res;
}
};
五、动态规划
1、思路解析:
1)在递归中,我们可以发现限制边界的条件有:
- 选择了某个数 x x x,作为其子数组的最后一个数
2)因此,我们应该循环枚举 x x x,作为最后一个数,并且改写:DFS方程->动态规划
3)边界处理:dp[0]=nums[0];
,表示遍历到第0个数时,最大子数组和为nums[0]
,
- 当
x<0
时默认为dp[]=0
2、代码解析:
//1、选择一个连续子数组,让其区间和最大
//2、选择了一个数,作为子数组的最后一个数
//3、选择一个数(4之前的数)作为数组中的最后一个数,使得其区间和最大
//答案=选择了之前的某个数的区间和+4
//4、边界->数组结束
const int N=2e5+7;
class Solution {
public:
int dp[N]; //表示第i个数时,最大子数组和
// int mem[200007];
// int dfs(vector<int>& nums,int x)
// {
// if(x<0) return 0;
// int ans=-1e9;
// //选择x之前的数作为其最后一个数
// //如果不能往下的话,最大值应该是它自己
// ans=max(nums[x],dfs(nums,x-1)+nums[x]);
// mem[x]=ans;
// return mem[x];
// }
int maxSubArray(vector<int>& nums)
{
int res=-1e9;
int n=nums.size();
// for(int i=0;i<n;i++) //枚举最后一个数
// {
// res=max(res,dfs(nums,i));
// }
dp[0]=nums[0]; //边界处理
for(int i=1;i<n;i++) //枚举最后一个数
{
dp[i]=max(nums[i],dp[i-1]+nums[i]);
}
for(int i=0;i<n;i++) res=max(res,dp[i]); //枚举选择哪个数作为最后一个数值最大
return res;
}
};