动态规划算法初识--斐波那契数列模型
动态规划类的题目一般需要明确五个流程 状态表示 状态转移方程 初始化 填表顺序 返回值
在题目的开始一般先定义一个dp表 其实就是一个名为dp的数组 数组里面值代表的意义就是状态表示 状态表示的明确来自于题目要求或者这类题目做多了后结合自己的经验
状态转移方程就是看dp[i]是怎么来的
一.第N个泰波那契数
题目解析
我们熟悉的斐波那契数是从第三个数开始每一个数是前两个数的和 泰波那契数是从第四个数开始 每一个数是前三个数的和
算法分析
对于这道题目dp表里面值的含义为某个泰波那契数的值
由题目知道 状态转移方程dp[i]=dp[i-1]]+dp[i-2]+dp[i-3]
初始化需要将前三个数初始化 填表顺序从左往右
根据题目要求知道返回值就是dp[n]
在写代码的时候需要处理边界情况 对于这个题目就是n是0 1 2的情况
代码实现
class Solution {
public:int tribonacci(int n) {int dp[38]={0};dp[0]=0;dp[1]=1,dp[2]=1; //初始化if(n==0) return 0;if(n==1||n==2) return 1; //边界情况for(int i=3;i<=n;i++){dp[i]=dp[i-1]+dp[i-2]+dp[i-3]; //填表}return dp[n];}
};
空间优化
这里的空间复杂度为O(N) 我们可以用滚动数组优化成O(1)
代码如下 只需给dp数组开四个空间就满足了需求
class Solution {
public:int tribonacci(int n) {int dp[4]={0};dp[0]=0;dp[1]=1,dp[2]=1;if(n==0) return 0;if(n==1||n==2) return 1;for(int i=3;i<=n;i++){dp[3]=dp[2]+dp[1]+dp[0];dp[0]=dp[1],dp[1]=dp[2],dp[2]=dp[3];}return dp[2];}
};
二.三步问题
题目解析
小孩一次可以走1阶2阶或者3阶楼梯 有n阶台阶 那么到达n阶楼梯一共有多少种方式
算法分析
可以从后往前推 要到达第i阶楼梯 只有三种方式①i-1->i ②i-2->i③i-3->i
所以到达第i阶的方法就可以转换为到第i-1阶楼梯的方法+到第i-2阶楼梯的方法+到第i-3阶楼梯的方法
所以这里dp表里面值的含义我们确立为到达某一台阶的方法
状态转移方程和第一题一样 dp[i]=dp[i-1]]+dp[i-2]+dp[i-3]
需要初始化前三个值
最后返回dp[n]
代码实现
class Solution {
public:int waysToStep(int n) {vector<int>dp(n+1);if(n==1) return 1; //边界情况处理if(n==2) return 2;if(n==3) return 4;const int con=1e9+7;dp[1]=1,dp[2]=2,dp[3]=4; //初始化 for(int i=4;i<=n;i++)dp[i]=((dp[i-1]+dp[i-2])%con+dp[i-3])%con; //填表return dp[n];}
};
同样也可以用滚动数组做空间优化 不过其实空间优化也不用每次都特别去考虑
三.使用最小花费爬楼梯
题目解析
给了我们一个数组cost 数组的下标代表第几节阶梯 里面的值代表向上走所需要花费的体力值 花费之后就可以向上走一个阶梯或者两个阶梯 我们可以选择从下标0或者下标1的位置开始 要计算到达楼顶的最小花费
算法分析
同样从结果开始考虑 要达到最后的位置 要么从i-1位置支付con2后走一步到达 要么从i-2位置支付con1后走两步到达
所以到达第i阶的最小体力值花费就可以转换为到达 第i-2阶最小花费+cost[i-2]和到达第i-1阶最小花费+const[i-1]中更小的那一个
已经给了我们const数组 知道了从该阶移动的花费 所以这里dp表中值的含义表示为 到达某一台阶的最小花费
状态转移方程为dp[i]=min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2])
初始化需要前两个位置的值 刚开始就是从前两个位置选一个出发 所以 dp[0]=0 dp[1]=0
最后返回的就是dp[n]
代码实现
class Solution {
public:int minCostClimbingStairs(vector<int>& cost) {int n=cost.size();int* dp=new int[n+1]; //dp[i]代表到达i+1台阶需要的最小花费dp[0]=0,dp[1]=0; //初始化for(int i=2;i<=n;i++) //costp[i]代表i+1台阶需要的花费dp[i]=min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);return dp[n];}
};
方法2
也可以将dp[i]表示为从该位置出发到达楼顶需要花费的最小值 来考虑
则初始化就需要将倒数第一二个位置初始化
从倒数第一个位置i-1出发花费con2到终点 dp[n-1]=const[n-1]
或者从i-2位置出发花费con1到达终点 dp[n-2]=cost[n-2]
则填表顺序就是从后往前填表 一直到了dp[0]和dp[1] 此时取两者中的最小值就是返回结果
class Solution {
public:int minCostClimbingStairs(vector<int>& cost) {int n=cost.size(); int* dp=new int[n+1]; //dp[i]表示从i位置出发 到达楼顶的最小花费dp[n-1]=cost[n-1],dp[n-2]=cost[n-2]; //有n个数 n-1就是最后一个元素for(int i=n-3;i>=0;i--){dp[i]=cost[i]+min(dp[i+1],dp[i+2]);}return min(dp[0],dp[1]);}
};
四.解码方法

题目解析
给了我们一个非空的字符串 里面都是0~9的数字 可以单独一个数字字符算一个数来解码 也可以两个数字组合为一个数来解码 数字1~26分别对应26个大写字母 返回可以解码的总数 如果没有合法的解码方式 返回0
算法分析
要注意0或者27~99都是不能解码的
dp表里面值的意义是 以某一位置结尾时总的解码数
状态转移方程为dp[i]=dp[i-1]+dp[i-2] dp[i-1]需要满足s[i]>0 dp[i-2]需要满足s[i-1]是1或者s[i-1]是2并且s[i]<7
初始化前两个位置 dp[0]为0或者1 dp[1]为0或者1或者2
如果s[0]是0的话 它自己不能单独编码也不能和后面的数编码成功 那么最终的结果一定编码失败的 直接返回0
s[0]是0的情况会直接结束 所以之后的处理中 s[0]一定不为0
则dp[0]=1
dp[1]最多为2 一种是第一第二个位置单独编码 一种是第一第二个位置组合起来编码 分别判断一下这两种情况是否存在
s[1] 如果不是0的话则第一个数和第二个数单独编码的情况就存在 则dp[1]++
另一种情况是s[0]和s[1]的数组合编码 则要求这个组合数大于等于10或者小于等于26则可以编码成功 则dp[1]++
初始化就完成了
接下来就按照从左往右的顺序来填表
代码实现
class Solution {
public:int numDecodings(string s) {int n=s.size();vector<int>dp(n); //dp[i]代表第i个位置为末尾有多少种编码方式//初始化+边界情况if(s[0]=='0')return 0; //第一个值是0的话 那么最终一定编码失败 这里没有返回则第一个值不为0dp[0]=1;if(n==1) return 1;if(s[1]!='0') // 第一第二个数单独编码的情况存在则++dp[1]++; if(s[0]=='1'||(s[0]=='2'&&s[1]<'7')) //第一二为合起来是合法的则++dp[1]++;for(int i=2;i<n;i++) //状态方程{if(s[i]>'0')dp[i]+=dp[i-1];if(s[i-1]=='1'||(s[i-1]=='2'&&s[i]<'7'))dp[i]+=dp[i-2];}return dp[n-1];}
};
处理边界问题及初始化问题的技巧
可以看到这道题目的初始化相较于之前长了很多 而且可以发现对于下标为1位置值的初始化和状态转移方程中的逻辑很相似 那我们可以考虑把下标为1的位置给放到状态转移方程中来完成
之前dp表和s 下标相同的一一对应 我们要把下标为1放到状态转移方程中 那么需要它前 两个位置的dp表 但此时1的前面只有一个0 没有下标-1 在下标0的前面应该还需要一个空间 所以把dp表里面的值整体向后移动一位 在开始就空出了一位
之前的dp[0]就是现在的dp[1] 此时dp[n]对应着s[n-1] 这里的dp[0]的初始化要使得d[2](之前下标为1的)在状态转移方程中根据不同条件的判断所得的结果和我们之前判断的结果一样
多数情况dp[0] 就是0 这里的dp[0]应该为1 因为在状态转移方程中第二个判断成立后 按照之前初始化的逻辑它就应该加1 这里是加dp[i-2] 对于此时也就是dp[0] 所以dp[0]应该初始化为1
在代码处理的时候 我们要注意映射关系 此时dp[i]映射s[i-1]
class Solution {
public:int numDecodings(string s) {int n=s.size();vector<int>dp(n+1); //dp[i]代表第i个位置为末尾有多少种编码方式//初始化+边界情况dp[0]=1;if(s[1-1]=='0') return 0; //dp在s中映射的位置是n-1的位置dp[1]=1;for(int i=2;i<=n;i++) //状态方程{if(s[i-1]>'0')dp[i]+=dp[i-1];if(s[i-2]=='1'||(s[i-2]=='2'&&s[i-1]<'7'))dp[i]+=dp[i-2];}return dp[n];}
};