动态规划理论基础
什么是动态规划
动态规划,英文:Dynamic Programming,简称DP,如果某一问题有很多重叠子问题,使用动态规划是最有效的。
所以动态规划中每一个状态一定是由上一个状态推导出来的,这一点就区分于贪心,贪心没有状态推导,而是从局部直接选最优的,
动态规划的解题步骤
- 确定dp数组(dp table)以及下标的含义
- 确定递推公式
- dp数组如何初始化
- 确定遍历顺序
- 举例推导dp数组
动态规划应该如何debug
相信动规的题目,很大部分同学都是这样做的。
看一下题解,感觉看懂了,然后照葫芦画瓢,如果能正好画对了,万事大吉,一旦要是没通过,就怎么改都通过不了,对 dp数组的初始化,递推公式,遍历顺序,处于一种黑盒的理解状态。
写动规题目,代码出问题很正常!
找问题的最好方式就是把dp数组打印出来,看看究竟是不是按照自己思路推导的!
斐波那契数 (通常用 F(n)
表示)形成的序列称为 斐波那契数列 。该数列由 0
和 1
开始,后面的每一项数字都是前面两项数字的和。也就是:
F(0) = 0,F(1) = 1 F(n) = F(n - 1) + F(n - 2),其中 n > 1
给定 n
,请计算 F(n)
。
示例 1:
输入:n = 2 输出:1 解释:F(2) = F(1) + F(0) = 1 + 0 = 1
示例 2:
输入:n = 3 输出:2 解释:F(3) = F(2) + F(1) = 1 + 1 = 2
示例 3:
输入:n = 4 输出:3 解释:F(4) = F(3) + F(2) = 2 + 1 = 3
提示:
0 <= n <= 30
思路:
这算是一道相当经典的题目了,首先我们先思考动规的五个步骤再进行代码的编辑
1.dp[i]代表第i个斐波那契数
2.递推公式为dp[i] = dp[i-1]+dp[i-2];
3.将dp初始化为dp[0] =0;dp[1] = 1;
4.遍历方式为从前往后遍历
5.打印dp数组
经由这五个步骤不难得出以下代码
class Solution {
public int fib(int n) {
//动态规划五部曲
//1.明确dp数组含义
//2.确认递推公式
//3.dp数组如何进行初始化
//4.确认遍历顺序
//5.打印dp数组
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];
}
}
假设你正在爬楼梯。需要 n
阶你才能到达楼顶。
每次你可以爬 1
或 2
个台阶。你有多少种不同的方法可以爬到楼顶呢?
示例 1:
输入:n = 2 输出:2 解释:有两种方法可以爬到楼顶。 1. 1 阶 + 1 阶 2. 2 阶
示例 2:
输入:n = 3 输出:3 解释:有三种方法可以爬到楼顶。 1. 1 阶 + 1 阶 + 1 阶 2. 1 阶 + 2 阶 3. 2 阶 + 1 阶
提示:
1 <= n <= 45
思路:
这一题思路比较有意思,枚举一下下列案例
爬到第一层楼梯有一种方法,爬到二层楼梯有两种方法。
那么第一层楼梯再跨两步就到第三层 ,第二层楼梯再跨一步就到第三层。
所以到第三层楼梯的状态可以由第二层楼梯 和 到第一层楼梯状态推导出来,那么就可以想到动态规划了。
依照动态规划的步骤我们可以写出以下代码
class Solution {
public int climbStairs(int n) {
if(n<=2)return n;
int[] dp = new int[n+1];
dp[1] = 1;
dp[2] = 2;
for(int i =3;i<=n;i++){
dp[i] = dp[i-1]+dp[i-2];
}
return dp[n];
}
}
给你一个整数数组 cost
,其中 cost[i]
是从楼梯第 i
个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。
你可以选择从下标为 0
或下标为 1
的台阶开始爬楼梯。
请你计算并返回达到楼梯顶部的最低花费。
示例 1:
输入:cost = [10,15,20] 输出:15 解释:你将从下标为 1 的台阶开始。 - 支付 15 ,向上爬两个台阶,到达楼梯顶部。 总花费为 15 。
示例 2:
输入:cost = [1,100,1,1,1,100,1,1,100,1] 输出:6 解释:你将从下标为 0 的台阶开始。 - 支付 1 ,向上爬两个台阶,到达下标为 2 的台阶。 - 支付 1 ,向上爬两个台阶,到达下标为 4 的台阶。 - 支付 1 ,向上爬两个台阶,到达下标为 6 的台阶。 - 支付 1 ,向上爬一个台阶,到达下标为 7 的台阶。 - 支付 1 ,向上爬两个台阶,到达下标为 9 的台阶。 - 支付 1 ,向上爬一个台阶,到达楼梯顶部。 总花费为 6 。
提示:
2 <= cost.length <= 1000
0 <= cost[i] <= 999
思路:
我们还是按照动态规划的解题步骤思考这一题
1.dp数组的含义
dp数组代表爬上第i个台阶所用的最小花费为dp[i]
2.递推公式
要想求得当前的dp[i]为最小值,思考这几种可能
一.从i-2的位置花费cost[i-2]到达了i,成本为dp[i-2]+cost[i-2]
二.从i-1的位置花费cost[i-1]到达了i,成本为dp[i-1]+cost[i-1]
除此之外没有其他可能了吧
那么我们想要花费最少,那么就需要计算这两个成本谁的花费最少,那么这个花费便是到达i台阶的最小花费
所以递推公式为
dp[i] = Math.min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2])
3.初始化dp数组
当i==0或者i==1时,因为是可以自由选择的
所以不用花费体力,故dp[0] =0,dp[1] =0
4.确认遍历方式
最上面的台阶需要从下面的台阶推导而来,所以应该是从前往后遍历dp数组
5.打印dp数组
经由以上思考我们不难得出以下代码
public int minCostClimbingStairs(int[] cost) {
//dp[i]表示登上第i个台阶最小的花费
//初始化为dp[1] = 0;dp[2] =0;
//因为可以自由选择从哪个台阶开始跳
//递推公式就是dp[i] = min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
//遍历方式是从前向后
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];
}
今天内容就是这些,感谢大家观看!