Day51 | Day50 | 动态规划 线性DP 最大子数组和&&不同的子序列
Day50 | 动态规划 :线性DP 最大子数组和&&不同的子序列
动态规划学习:
1.思考回溯法(深度优先遍历)怎么写
注意要画树形结构图
2.转成记忆化搜索
看哪些地方是重复计算的,怎么用记忆化搜索给顶替掉这些重复计算
3.把记忆化搜索翻译成动态规划
基本就是1:1转换
53.最大子数组和
思路分析(子问题):
定义dp[i]表示以 nums[i] 结尾的最大子数组和
分类讨论:
1.nums[i]
dp[i]=nums[i];
2.nums[i]和前面的数一起组成最大子数组和
dp[i]=dp[i-1]+nums[i];
两种情况取最大值即可
动态规划:
注:
1.初始化是dp[0]=nums[0],因为第一天最大就是自己了
2.最后需要在遍历一次dp数组获得最大值,因为在dp数组更新过程中的for并不包含dp[0]
1 | class Solution { |
115.不同的子序列
思路分析(子问题):
dfs(i,j)为s的前i个字符里面有多少个t的前j个字符组成的子序列
还是s[i]和t[j]选或不选的问题,只是这次不是插入删除的操作数,也不是判断是不是子序列,也不是求和,只是换成了子序列的数量
还是依据选或不选的思路,我们看选或不选s[i]
如果不选s[i],那说明s[i]!=t[j]
1 | dfs(i,j)=dfs(i-1,j); |
因为我没选s[i],那我s的前i个字符里面有多少个t的前j个字符组成的子序列和s的前i-1个字符里面有多少个t的前j个字符就是一个东西了
如果选s[i],那说明s[i]==t[j]
1 | dfs(i,j)=dfs(i-1,j)+dfs(i-1,j-1); |
因为我选了s[i],那我s的前i个字符里面有多少个t的前j个字符组成的子序列和s的前i-1个字符里面有多少个t-1的前j个字符就是一个东西了,因为s[i]==t[j],可以不看这两个
而由于加法原理,这里面只有选或不选两种情况,我们所有的方案数量就是把选和不选的情况都加起来
不选s[i]没法加dfs(i-1,j-1),因为s[i]!=t[j]
递归边界
如果t为空串,那一定是s的子序列并且只有一个选法可以得到空串,那就是什么都不选,所以数量为1,返回1
如果s为空串,那s不管怎么样都找不到一个t,返回0
1.回溯
注意终止条件两个if的顺序
1 | if(j<0) |
一定要先检查t的下标j,再检查s的下i
举个例子说明:
当两者都为空串,即dfs(-1,-1)时,先判断j返回数量为1
先判断i返回为0
而我们知道这个是1才对
这就是因为当i=-1时,可能t还没有遍历结束,而我们从下往上返回返回的都是0的话,就代表我们在这些分支上没有找到过正确的答案,而很有可能正确答案是存在的,这样就会漏掉很多合法的子序列从而导致错误
1 | class Solution { |
2.记忆化搜索
就是搞一个哈希表dp,全都初始化为-1,每次返回前给哈希表dp赋值,碰到不是-1的那就是算过的,那就直接返回计算过的结果,不需要再次递归了
1 | class Solution { |
3.动态规划
注意我们的i=0和j=0是表示的dfs中i<0和j<0的状态,即s为空串或者t为空串,对应的是递归边界
所以dp数组的下标从1开始记录答案
1 | class Solution { |