动态规划1|509. 斐波那契数|70. 爬楼梯|746. 使用最小花费爬楼梯
一、动态规划理论基础
-
动态规划中每一个状态一定是由上一个状态推导出来的,这一点就区分于贪心,贪心没有状态推导,而是从局部直接选最优的。
-
动态规划问题解题五部曲:
a. 确定dp数组(dp table)以及下标的含义
b. 确定递推公式
c. dp数组如何初始化
d. 确定遍历顺序
e. 举例推导dp数组
因为一些情况是递推公式决定了dp数组要如何初始化
-
动态规划问题debug:找问题的最好方式就是把dp数组打印出来,看看究竟是不是按照自己思路推导的。做动规的题目,写代码之前一定要把状态转移在dp数组的上具体情况模拟一遍,心中有数,确定最后推出的是想要的结果。
二、509. 斐波那契数
题目连接:509. 斐波那契数 - 力扣(LeetCode)
- 确定dp[i]含义:第 i 个斐波那契数的值为dp[i]
- 确定递推公式:dp[i] = dp[i -1] + dp[i - 2];
- dp初始化:dp[0] = 0 ; dp[1] = 1;
- 确定遍历顺序:从前往后遍历
- 打印 dp 数组
- 时间复杂度:O(n)
- 空间复杂度:O(n)
class Solution {
public int fib(int n) {
if (n == 0) return 0;
if (n == 1) return 1;
int[] dp = new int[n + 1];
dp[0] = 0;
dp[1] = 1;
for(int i = 2; i <= n; i++){
dp[i] = dp[i -1] + dp[i - 2];
}
return dp[n];
}
}
只需要维护两个数值就可以了,不需要记录整个序列。
- 时间复杂度:O(n)
- 空间复杂度:O(1)
class Solution {
public int fib(int n) {
if (n < 2) return n;
int dp[] = new int[2];
dp[0] = 0;
dp[1] = 1;
for (int i = 2; i <= n; i++) {
int sum = dp[0] + dp[1];
dp[0] = dp[1];
dp[1] = sum;
}
return dp[1];
}
}
三、70. 爬楼梯
- 确定dp[i]含义:爬到 i 阶楼顶的方法种数为dp[i]
- 确定递推公式:dp[i] = dp[i -1] + dp[i - 2];
- dp初始化:dp[0] = 1 ; dp[1] = 1;
- 确定遍历顺序:从前往后遍历
class Solution {
public int climbStairs(int n) {
if(n < 2) return 1;
int[] dp = new int[n + 1];
dp[0] = 1;
dp[1] = 1;
for(int i = 2; i <= n; i++){
dp[i] = dp[i - 1] + dp[i - 2];
}
return dp[n];
}
}
四、746. 使用最小花费爬楼梯
题目连接:746. 使用最小花费爬楼梯 - 力扣(LeetCode)
- 确定dp[i]含义:到达 i 位置的最小花费为dp[i]
- 确定递推公式:dp[i] = Math.min( dp[i -1] + cost[i - 1],dp[i - 2] + cost[i - 2] );
- dp初始化:dp[0] = 0 ; dp[1] = 0;
- 确定遍历顺序:从前往后遍历
class Solution {
public int minCostClimbingStairs(int[] cost) {
int[] dp = new int[cost.length + 1];
dp[0] = 0;
dp[1] = 0;
for(int i = 2; i <= cost.length; i++){
dp[i] = Math.min(dp[i - 1] + cost[ i - 1], dp[i - 2] + cost[i - 2]);
}
return dp[cost.length];
}
}