代码随想录算法Day38 | 动态规划理论基础 ,509. 斐波那契数 ,70. 爬楼梯 ,746. 使用最小花费爬楼梯
动态规划理论基础
动态规划五步曲:
- 确定dp数组(dp table)以及下标的含义
- 确定递推公式
- dp数组如何初始化
- 确定遍历顺序
- 举例推导dp数组
509. 斐波那契数
题目链接:509. 斐波那契数 - 力扣(LeetCode)
思路
- 确定dp数组以及下标的含义
dp[i]的定义为:第i个数的斐波那契数值是dp[i]
- 确定递推公式
状态转移方程 dp[i] = dp[i - 1] + dp[i - 2];
- dp数组如何初始化
dp[0] = 0;
dp[ 1 ] = 1;
- 确定遍历顺序
从递归公式dp[i] = dp[i - 1] + dp[i - 2];中可以看出,dp[i]是依赖 dp[i - 1] 和 dp[i - 2],那么遍历的顺序一定是从前到后遍历的
- 举例推导dp数组
按照这个递推公式dp[i] = dp[i - 1] + dp[i - 2],我们来推导一下,当N为10的时候,dp数组应该是如下的数列:
0 1 1 2 3 5 8 13 21 34 55
代码
1 class Solution { 2 public int fib(int n) { 3 if (n < 2) return n; 4 int a = 0, b = 1, c = 0; 5 for (int i = 1; i < n; i++) { 6 c = a + b; 7 a = b; 8 b = c; 9 } 10 return c; 11 } 12 }
1 //非压缩状态的版本 2 class Solution { 3 public int fib(int n) { 4 if (n <= 1) return n; 5 int[] dp = new int[n + 1]; 6 dp[0] = 0; 7 dp[1] = 1; 8 for (int index = 2; index <= n; index++){ 9 dp[index] = dp[index - 1] + dp[index - 2]; 10 } 11 return dp[n]; 12 } 13 }
70. 爬楼梯
题目链接:70. 爬楼梯 - 力扣(LeetCode)
思路
在这道题中,可以明显看出 爬第一层有 1 种方法,爬第二层有 2 种方法。
到第三层时,由于可以爬 1 或 2 个台阶,所以可以在第一层基础上爬 2 个台阶到第三层,也可以在第二层的基础上爬 1 个台阶到第三层。
由此可见第三层楼梯的状态可以由第二层楼梯 和 到第一层楼梯状态推导出来,那么就可以想到动态规划了。
- 确定dp数组以及下标的含义
dp[i]: 爬到第i层楼梯,有dp[i]种方法
- 确定递推公式
dp[i] = dp[i - 1] + dp[i - 2] 。
在推导dp[i]的时候,一定要时刻想着dp[i]的定义,否则容易跑偏。
这体现出确定dp数组以及下标的含义的重要性!
- dp数组如何初始化
dp[1] = 1;
dp[2] = 2;
- 确定遍历顺序
从递推公式dp[i] = dp[i - 1] + dp[i - 2];中可以看出,遍历顺序一定是从前向后遍历的
- 举例推导dp数组
举例当n为5的时候,dp table(dp数组)应该是这样的
代码
1 // 常规方式 2 public int climbStairs(int n) { 3 int[] dp = new int[n + 1]; 4 dp[0] = 1; 5 dp[1] = 1; 6 for (int i = 2; i <= n; i++) { 7 dp[i] = dp[i - 1] + dp[i - 2]; 8 } 9 return dp[n]; 10 }
1 // 用变量记录代替数组 2 class Solution { 3 public int climbStairs(int n) { 4 if(n <= 2) return n; 5 int a = 1, b = 2, sum = 0; 6 7 for(int i = 3; i <= n; i++){ 8 sum = a + b; // f(i - 1) + f(i - 2) 9 a = b; // 记录f(i - 1),即下一轮的f(i - 2) 10 b = sum; // 记录f(i),即下一轮的f(i - 1) 11 } 12 return b; 13 } 14 }
746. 使用最小花费爬楼梯
题目链接:746. 使用最小花费爬楼梯 - 力扣(LeetCode)
思路
- 确定dp数组以及下标的含义
dp[i]的定义:到达第i台阶所花费的最少体力为dp[i]。
- 确定递推公式
题目要求的是最少体力,所以dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
- dp数组如何初始化
首先明白题意,跳到下标 0 或者 1 时不需要花费体力的,但从 0 或者 1 跳到其他层时需要花费当前下标对应的数字。所以:
dp[0] = 0,dp[1] = 0;
- 确定遍历顺序
因为是模拟台阶,而且dp[i]由dp[i-1]dp[i-2]推出,所以是从前到后遍历cost数组就可以了。
- 举例推导dp数组
拿示例2:cost = [1, 100, 1, 1, 1, 100, 1, 1, 100, 1] ,来模拟一下dp数组的状态变化,如下:
代码
1 // 方式一:第一步不支付费用 2 class Solution { 3 public int minCostClimbingStairs(int[] cost) { 4 int len = cost.length; 5 int[] dp = new int[len + 1]; 6 7 // 从下标为 0 或下标为 1 的台阶开始,因此支付费用为0 8 dp[0] = 0; 9 dp[1] = 0; 10 11 // 计算到达每一层台阶的最小费用 12 for (int i = 2; i <= len; i++) { 13 dp[i] = Math.min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]); 14 } 15 16 return dp[len]; 17 } 18 }
1 // 扩展 2 // 方式二:第一步支付费用 最后一步不需要费用 3 class Solution { 4 public int minCostClimbingStairs(int[] cost) { 5 int[] dp = new int[cost.length]; 6 dp[0] = cost[0]; 7 dp[1] = cost[1]; 8 for (int i = 2; i < cost.length; i++) { 9 dp[i] = Math.min(dp[i - 1], dp[i - 2]) + cost[i]; 10 } 11 //最后一步,如果是由倒数第二步爬,则最后一步的体力花费可以不用算 12 return Math.min(dp[cost.length - 1], dp[cost.length - 2]); 13 } 14 }
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 震惊!C++程序真的从main开始吗?99%的程序员都答错了
· 【硬核科普】Trae如何「偷看」你的代码?零基础破解AI编程运行原理
· 单元测试从入门到精通
· 上周热点回顾(3.3-3.9)
· winform 绘制太阳,地球,月球 运作规律