今日总结
1、动态规划的五部曲要复习
2、动态规划的dp数组的含义及下标一定要定义清楚
3、动态规划的dp数组的递推公式一定要根据题目进行分析
4、动态规划的初始化一定要分析清楚(一定要在递推公式确定后再进行初始化分析)
动态规划的基础理论
1、动态规划是什么
动态规划的英文:Dynamic Programming,简称DP,**如果某一个问题有很多的重叠子问题,**使用动态规划是最有效的,
所以动态规划中每一个状态一定是由上一个状态推导出来的,这一点就区分与贪心,贪心没有状态推导,而是从局部直接选择最优
2、动态规划的解题步骤
状态转移公式(递推公式)很重要,但是动态规划不仅仅只有递推公式
动态规划的五部曲
(1)确定dp数组(dp table)以及下标的含义
(2)确定递推公式
(3)dp数组如何初始化
(4)确定遍历顺序
(5)举例推导dp数组
先确定递推公式再考虑初始化的原因:
一些情况是递推公式决定dp数组如何初始化
3、动态规划如何debug
最好的方式就是把dp数组打印出来,看看是不是按照自己思路推导的
(1)做动态规划的题目,写代码前一定要把状态转移在dp数组上的具体情况模拟一遍,做到心中有数,确定最后推出的是想要的结果
(2)然后再写代码,如果代码没通过就打印dp数组,看看是不是和自己预先推导的一样。
(3)如果打印的dp数组和自己推导的一样,就是自己的递归公式、初始化、遍历顺序有问题
(4)如果和自己预先模拟推导的不一,就是代码的实现细节有问题
斐波那契数
题目链接:509. 斐波那契数 - 力扣(LeetCode)
整体思路:
1、使用递归的方式做
2、使用动态规划dp做
dp五部曲
(1)确定dp数组及下标的含义
dp[i]的定义为:第i个数的斐波那契数值为dp[i];
(2)确定递推公式(就是当前的dp值与前边的有什么关系)
状态转移方程dp[i] = dp[i-1]+dp[i-2];
(3)qp数组初始化
dp[0]=0,dp[1]=1;
(4)确定遍历顺序
从状态转移方程可以看出当前dp[i]依赖于i-1,i-2,所以是从前往后遍历
(5)举例推导dp数组
当N=10:0,1,1,2,3,5,8,13,21,34,55
整体代码:
1、递归
cppclass Solution { public: //1、确定递归的返回值、输入值 //返回值当前的计算结果 //输入值:当前的N int digui(int N) { //确定停止条件 if(N==1)return 1; else if(N==0)return 0; //确定单层递归逻辑 return digui(N-1)+digui(N-2); } int fib(int n) { return digui(n); } };
2、动态规划(dp)
cppclass Solution { public: int fib(int n) { //1、确定dp数组及下标含义:下标为当前的斐波那契数值 vector<int>dp(n+1,0); //2、确定递推公式dp[i] = dp[i-1]+dp[i-2] //3、dp数组初始化 if(n<1)return 0; dp[0]=0; dp[1]=1; if(n<=1)return n; //4、确定遍历顺序:前-->后 //5、举例推导 for(int i=2;i<n+1;i++) { dp[i] = dp[i-1]+dp[i-2]; } return dp[n]; } };
爬楼梯
整体思路:
第一层楼梯:1
第二层楼梯:1+1;2
第三层楼梯:1+1+1(2,1,1);2+1(2,2,1);1+2(1,1,2)
第四层楼梯:1+1+1+1(3,1,1);2+1+1(3,2,1);1+2+1(3,3,1);1+1+2(2,1,2);2+2(2,2,2)
可以看出,当前i层的楼梯是通过i-1层的楼梯+1或者i-2层的楼梯+2的全部方法,所以当前层是与前边层的状态有关的,可以使用动态规划dp
(1)确定dp数组及下标
dp数组是当前楼梯的方法数,下标是当前楼梯的层数+1
(2)确定dp数组的递推公式
dp[i] = dp[i-1]+dp[i-2]
(3)确定dp数组的初始化
dp[1]=1.dp[2]=2
(4)确定dp数组遍历方向:从前往后
(5)举例
整体代码:
cppclass Solution { public: int climbStairs(int n) { //(1)确定dp数组及下标 //dp数组是当前楼梯的方法数,下标是当前楼梯的层数 vector<int>dp(n+1,0); if(n==1)return 1; //(2)确定dp数组的递推公式 // dp[i] = dp[i-1]+dp[i-2] //(3)确定dp数组的初始化 //dp[1]=1.dp[2]=2 dp[1]=1; dp[2]=2; for(int i=3;i<n+1;i++) { dp[i] = dp[i-1]+dp[i-2]; } //(4)确定dp数组遍历方向:从前往后 return dp[n]; //(5)举例 } };
使用最小花费爬楼梯
题目链接:746. 使用最小花费爬楼梯 - 力扣(LeetCode)
整体思路:
由题目可知:
(1)可以从0级台阶、1级台阶进行向上爬1级或者两级,支付当前台阶的cost
(2)最终到达楼梯顶,也就是需要通过最后一级台阶或者倒二台阶
(3)需要花费最少的cost,
(4)所以当前层最少一定是前边花费的最小-->动态规划
使用dp动态规划
(1)dp数组的含义:dp[i]表示当前层花费最少的cost
(2)dp数组的状态转移方程
dp[i] = min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2])
(3)dp数组的初始化
dp[0]=cost[0];
dp[1] =cost[1];
(4)dp数组的遍历方式:从前到后
(5)举例
整体代码:
cppclass Solution { public: int minCostClimbingStairs(vector<int>& cost) { /* (1)dp数组的含义:dp[i]表示当前层花费最少的cost (2)dp数组的状态转移方程 dp[i] = min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]) (3)dp数组的初始化 dp[0]=cost[0]; dp[1] =cost[1]; (4)dp数组的遍历方式:从前到后 */ vector<int>dp(cost.size()+1); dp[0] = 0; dp[1] = 0; for(int i=2;i<cost.size()+1;i++) { dp[i] = min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]); } return dp[cost.size()]; } };