DP理论基础
动态是由前一个推导出来的,而贪心是局部直接选最优的。
解题步骤:
- 确定dp 数组中以及下标中的含义
- 确递推公式
- dp数组如何初始化
- 确定遍历顺序
- 举例推导dp数组
dp如何debug:
做dp题目时,写代码之前要先把状态转移在dp数组上的具体情况模拟一遍,看是否能推导出正确结果。然后再写代码,打印dp数组,看是否和自己推导的过程一样。如果推导过程一样,那么就是递归公式,初始化或者遍历顺序有问题。若不一样,则代码实现细节有问题。
发出这样的问题之前,其实可以自己先思考这三个问题:
- 这道题目我举例推导状态转移公式了么?
- 我打印dp数组的日志了么?
- 打印出来了dp数组和我想的一样么?
509.斐波那契数
代码随想录
题目条件:F(0) = 0,F(1) = 1;F(n) = F(n-1) + F(n-2) ,其中 n > 1;
dp五部曲:
- 确定dp数组以及下标的含义
- dp[i] 的定义为第 i 个数的斐波那契数值 dp[i]
- 确定dp公式
- 状态转移方程: dp[i] = dp[i-1] +dp[i-2];
- dp数组如何初始化
- dp[0] = 0;dp[1] =1;
- 确定遍历顺序
- dp[i] 是依赖 dp[i-1] 和dp[i-2] , 从前向后遍历
- 举例推导dp数组
- 当N为10时:
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 |
dp[i] | 0 | 1 | 1 | 2 | 3 | 5 | 8 | 13 | 21 | 34 | 55 |
代码
初版代码:
class Solution {
public int fib(int n) {
if(n<=1)return n;
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];
}
}
其实可以只用维护两个数组
class Solution {
public int fib(int n) {
if(n<=1)return n;
int[] dp = new int[2];
dp[0] = 0;
dp[1] = 1;
for (int i = 2; i <= n; i++) {
int newSum = dp[0] + dp[1];
dp[0] = dp[1];
dp[1] = newSum;
}
return dp[1];
}
}
70.爬楼梯
代码随想录
一步可以迈一个或者两个台阶,爬到n阶后又有多少种方法
n阶 | 1 | 2 | 3 |
方法数 | 1 | 2 | 3 |
三阶只和一阶和二阶有关,因为一步只能迈一阶或者二阶,所以你不是从一阶迈上来的,就是从二阶迈上来的。1阶到三阶,只能走两阶步,二阶到三阶只能走一阶步,所以到达三阶的方法就是一阶方法数与二阶方法数之和。同理四阶只能由二阶和三阶推导上来。
我的理解是:题目中要求的每次可以爬1或者2个台阶,也就是说,最终到达n阶台阶有两种方式,一个是爬1阶台阶到达(对应的是从n-1阶台阶开始),那么另一个就是爬2阶台阶到达(对应的是从n-2阶台阶开始爬),而爬n-1阶和n-2阶台阶的方法有dp【n-1】,dp【n-2】个。所以最终爬n阶台阶的方法种类就是dp【n-1】+dp【n-2】。其实也对应了卡尔所说的从n-1和n-2阶爬上去,探究的是几种走法,而不是几步。——B站lveryangg
dp五部曲:
- dp[i]表示到达第 i 阶有dp[i] 种方法
- 状态转移方程 dp[i] = dp[i-1] +dp[i-2];
- dp[1] =1 ,dp[2] = 2;不用初始化dp[0]],根据dp[i]定义初始化是没有意义的
- 遍历顺序从前向后遍历
- 举例数组是斐波那契数列
代码:
实际就是斐波那契数列的代码。
class Solution {
public int climbStairs(int n) {
if(n<=2)return n;
int[] dp = new int[2];
dp[0] = 1;
dp[1] = 2;
for (int i =3;i<=n;i++){
int newSum = dp[0]+dp[1];
dp[0] = dp[1];
dp[1] =newSum;
}
return dp[1];
}
}
746.使用最小花费爬楼梯
代码随想录:
仿照第70题爬楼梯,在是爬至第i层楼梯的上一步,我们只有两个选择,走一步或者走两步。
如果走一步,我们需要花费dp[i-1]+cost[i-1];如果走两步,我们需要花费dp[i-2]+cost[i-2];
取两者种最小值就是到达此阶梯的最小值。
dp五部曲:
- dp[i] 表示爬到第i个台阶所需支付的最低费用
- dp[i] = Math.min(dp[i-1]+cost[i-1] , dp[i-2]+cost[i-2] )
- dp[0] = 0,dp[1] = 0;
- 从前向后遍历
- 实例数组
关于初始化dp[0] = 0,dp[1] = 1;我最初写为dp[0] = cost[0],dp[1] = cost[1]。需要注意题中描述:
其中
cost[i]
是从楼梯第i
个台阶向上爬需要支付的费用。你可以选择从下标为
0
或下标为1
的台阶开始爬楼梯。
也就是到达第0个台阶或者第1个台阶是不收费的,花费是0.只有向上爬才收费。
第i个阶梯 | 0 | 1 | 爬到顶楼 |
cost[i],从第i个阶梯向上爬需要支付的费用 | 10 | 15 | null |
dp[i],爬到第i个阶梯,所需的最低花费 | 0 | 0 | 10 |
第i个阶梯 | 0 | 1 | 2 | 楼梯顶部 |
cost[i],从第i个阶梯向上爬需要支付的费用 | 10 | 15 | 20 | null |
dp[i],爬到第i个阶梯,所需的最低花费 | 0 | 0 | 10 | 15 |
第i个阶梯 | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 楼梯顶部 |
cost[i],从第i个阶梯向上爬需要支付的费用 | 1 | 100 | 1 | 1 | 1 | 100 | 1 | 1 | 100 | 1 | null |
dp[i],爬到第i个阶梯,所需的最低花费 | 0 | 0 | 1 | 2 | 2 | 3 | 3 | 4 | 4 | 5 | 6 |
代码:
class Solution {
public int minCostClimbingStairs(int[] cost) {
if(cost.length<=1)return 0;
int[] dp = new int[2];
for(int i=2; i<=cost.length;i++){
int newSum = Math.min(dp[1] + cost[i - 1], dp[0] + cost[i - 2]);
dp[0]=dp[1];
dp[1] = newSum;
System.out.println(dp[1]);
}
return dp[1];
}
}