动态规划理论基础
动态规划,英文:Dynamic Programming,简称DP,如果某一问题有很多重叠子问题,使用动态规划是最有效的。
所以动态规划中每一个状态一定是由上一个状态推导出来的,这一点就区分于贪心,贪心没有状态推导,而是从局部直接选最优的。
动态规划的解题步骤
- 确定dp数组(dp table)以及下标的含义
- 确定递推公式
- dp数组如何初始化
- 确定遍历顺序
- 举例推导dp数组
动态规划的debug技巧
打印dp数组,查看推导过程是否和设想一致。
LeetCode 509 斐波那契数
思路
按照五个步骤分析:
- 确定dp数组(dp table)以及下标的含义:dp[i]代表第i个斐波那契数
- 确定递推公式:dp[i] = dp[i-1] + dp[i-2]
- dp数组如何初始化:dp[0]=0, dp[1]=1
- 确定遍历顺序:从3开始由小到大遍历到n
- 举例推导dp数组:假设n为10,dp数组应该为0,1,1,2,3,5,8,13,21,34,55
解法
class Solution {
public int fib(int n) {
int[] dp = new int[n+1];
dp[0] = 0;
if (n > 0) {
dp[1] = 1;
}
for (int i = 2; i <= n; i++) {
dp[i] = dp[i-1] + dp[i-2];
}
return dp[n];
}
}
LeetCode 70 爬楼梯
思路
假设你正在爬楼梯。需要 n 阶你才能到达楼顶。
每次你可以爬 1 或 2 个台阶。你有多少种不同的方法可以爬到楼顶呢?
爬到1层有1种方法,爬到2层有两种方法,而爬到3层有几种方法呢?要爬到第3层,要么在第1层走2步,要么在第二层走1步。所以当前的状态和之前的状态有关,我们可以用动态规划。
- 确定dp数组(dp table)以及下标的含义:dp[i]指爬到第i层有几种方法
- 确定递推公式:dp[i] = dp[i-1] + dp[i-2]
- dp数组如何初始化:dp[1] = 1,dp[2] = 2
- 确定遍历顺序:根据递推公式,高层状态根据低层推导,所以由小到大遍历
- 举例推导dp数组:假设爬到第10层,dp数组应该是1,2,3,5,8,13,21,34,55,89 由于数组下标从0开始,我们此处为了方便,dp[0]设置为1(满足递推公式)
解法
class Solution {
public int climbStairs(int n) {
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];
}
}
LeetCode 746 使用最小花费爬楼梯
思路
给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。
你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。
请你计算并返回达到楼梯顶部的最低花费。
要爬到下标为2台阶,可以从0开始爬并支付cost[0],或者从1开始爬支付cost[1]。而我们需要最小花费,那么就在其中选取最小值。
- 确定dp数组(dp table)以及下标的含义:dp[i]指爬到第i层最小的花费。题目要求爬到顶部,所以dp长度比cost大1
- 确定递推公式:dp[i] = min(dp[i-1]+cost[i-1], dp[i-2]+cost[i-2])
- dp数组如何初始化:dp[0] = 0,dp[1] = 0
- 确定遍历顺序:根据递推公式,高层状态根据低层推导,所以由小到大遍历
- 举例推导dp数组:假设cost数组为10,15,20。dp[2]=min(10,15)=10,dp[3]=min(10+20, 0+15)=20
解法
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 < dp.length; i++) {
dp[i] = Math.min(dp[i-1]+cost[i-1], dp[i-2]+cost[i-2]);
}
return dp[dp.length-1];
}
}
今日收获总结
今日学习2小时,要用动态规划的标志就是当前状态可以由前面的状态推导