1 今日打卡
斐波那契数 509. 斐波那契数 - 力扣(LeetCode)
使用最小花费爬楼梯 746. 使用最小花费爬楼梯 - 力扣(LeetCode)
2 dp基础理论
dp五部曲:
确定dp数组(dp table)以及下标的含义
确定递推公式
dp数组如何初始化
确定遍历顺序
举例推导dp数组
3 斐波那契数
3.1 思路
第一步:
dpi 表示斐波那契数列的第 i 项的值;
下标i 对应斐波那契数列的项数(从 0 开始),比如dp0是第 0 项,dp1是第 1 项,dp2是第 2 项,以此类推。
第二步:
标准形式:dpi = dpi-1 + dpi-2(i ≥ 2),入门题目。因为直接给出了转移方程
第三步:
第 0 项:dp0 = 0;
第 1 项:dp1 = 1
第四步:
状态转移方程中,dpi依赖dpi-1和dpi-2(前两项),因此必须从前往后遍历(从 2 到 n),才能保证计算当前项时,前两项已经有正确的值。
第五步:
以n=5为例,手动推导代码的执行过程,验证逻辑正确性:
初始状态:dp0 = 0,dp1 = 1(对应第 0、1 项);
i=2:
sum = 0+1 = 1 → dp 0 = 1(原 dp 1),dp 1 = 1(第 2 项);
i=3:
sum = 1+1 = 2 → dp 0 = 1(原 dp 1),dp 1 = 2(第 3 项);
i=4:
sum = 1+2 = 3 → dp 0 = 2(原 dp 1),dp 1 = 3(第 4 项);
i=5:
sum = 2+3 = 5 → dp 0 = 3(原 dp 1),dp 1 = 5(第 5 项);
最终返回dp1 = 5,符合斐波那契数列第 5 项的结果(0,1,1,2,3,5)。
3.2 实现代码
java
class Solution {
public int fib(int n) {
int[] dp = new int[2];
dp[0] = 0;
dp[1] = 1;
if(n < 2) return dp[n];
for(int i = 2; i <= n; i++) {
int sum = dp[0] + dp[1];
dp[0] = dp[1];
dp[1] = sum;
}
return dp[1];
}
}
4 爬楼梯
4.1 思路
第一步:
dpi 表示:爬到第 i 级台阶时,一共有多少种不同的方法;
第二步:
要爬到第i级台阶,最后一步只有两种可能:
从第i-1级台阶爬 1 级上来;
从第i-2级台阶爬 2 级上来。
状态转移方程:dpi = dpi-1 + dpi-2(i ≥ 2),即爬到第i级的方法数 = 爬到i-1级的方法数 + 爬到i-2级的方法数。
第三步:
dp0 = 1:爬到第 0 级台阶(地面)只有 1 种方法(原地不动),这是为了让状态转移方程在i=2时成立(dp2 = dp1 + dp0 = 1 + 1 = 2,符合实际:爬 2 级台阶有 "1+1""2" 两种方法);
dp1 = 1:爬到第 1 级台阶只有 1 种方法(直接爬 1 级);
第四步:
状态转移方程中,dpi依赖dpi-1和dpi-2(前两级的结果),因此必须从前往后遍历(从 2 到 n),保证计算dpi时,dpi-1和dpi-2已经有正确的值。
第五步:
以n=5为例,手动推导验证逻辑正确性:
初始状态:dp0 = 1,dp1 = 1;
i=2:dp2 = dp1 + dp0 = 1 + 1 = 2(方法:1+1、2);
i=3:dp3 = dp2 + dp1 = 2 + 1 = 3(方法:1+1+1、1+2、2+1);
i=4:dp4 = dp3 + dp2 = 3 + 2 = 5;
i=5:dp5 = dp4 + dp3 = 5 + 3 = 8;
最终dp5 = 8,即爬 5 级台阶有 8 种不同方法,符合实际逻辑。
4.2 实现代码
java
class Solution {
public int climbStairs(int n) {
// 1. 定义dp数组:dp[i]表示爬到第i级台阶的方法数
// 数组长度为n+1,因为要覆盖0到n级台阶
int[] dp = new int[n + 1];
// 2. 初始化dp数组
dp[0] = 1; // 爬到0级(地面)只有1种方法(原地不动)
dp[1] = 1; // 爬到1级台阶只有1种方法(直接爬1级)
// 边界处理:n=1时直接返回结果,无需后续计算
if(n == 1) return 1;
// 3. 确定遍历顺序:从2到n正向遍历,因为dp[i]依赖dp[i-1]和dp[i-2]
for(int i = 2; i <= n; i++) {
// 4. 状态转移方程:爬到i级的方法数 = 爬到i-1级 + 爬到i-2级的方法数
dp[i] = dp[i-2] + dp[i-1];
}
// 5. 返回爬到第n级台阶的方法数
return dp[n];
}
}
5 使用最小花费爬楼梯
5.1 思路
第一步:
dp i 及其下标 i 的含义
dpi 表示:到达第 i 级台阶(准备向上爬)时,所需要花费的最小总代价;
第二步:
到达第i级台阶有两种途径:
从第i-1级台阶爬 1 级上来:总代价 = 到达i-1级的最小代价 + 爬i-1级的代价 → dpi-1 + costi-1;
从第i-2级台阶爬 2 级上来:总代价 = 到达i-2级的最小代价 + 爬i-2级的代价 → dpi-2 + costi-2;
状态转移方程:dpi = Math.min(dpi-1 + costi-1, dpi-2 + costi-2)(i ≥ 2),即取两种途径中代价更小的那个。
第三步:
dp0 = 0:到达第 0 级台阶(起点),不需要花费任何代价;
dp1 = 0:到达第 1 级台阶(起点),也不需要花费任何代价;
这两个初始化值是题目规则的直接体现,也是后续计算的基础。
第四步:
状态转移方程中,dpi依赖dpi-1和dpi-2(前两级的最小代价),因此必须从前往后遍历(从 2 到len),保证计算dpi时,前两级的结果已经是确定的最小值。代码中的遍历逻辑:for(int i = 2; i <= len; i++),从第 2 级台阶开始,一直计算到楼顶(len级)。
第五步:
以cost = 10, 15, 20为例(台阶数len=3,目标爬到第 3 级即楼顶),手动推导验证:
初始状态:dp0 = 0,dp1 = 0;
i=2(到达第 2 级台阶):
dp2 = min(dp1+cost1, dp0+cost0) = min(0+15, 0+10) = 10;
i=3(到达楼顶):
dp3 = min(dp2+cost2, dp1+cost1) = min(10+20, 0+15) = 15;
最终返回dp3 = 15,即最小代价为 15(路径:从第 1 级台阶直接爬 2 级到楼顶,花费 15),符合实际逻辑。
5.2 实现代码
java
class Solution {
public int minCostClimbingStairs(int[] cost) {
// 获取台阶的总数(cost数组长度 = 台阶数)
int len = cost.length;
// 1. 定义dp数组:dp[i]表示到达第i级台阶时的最小总代价
// 数组长度为len+1,因为要计算到"楼顶"(第len级)的代价
int[] dp = new int[len + 1];
// 2. 初始化dp数组:从0级或1级台阶出发无需花费代价
dp[0] = 0; // 到达0级台阶(起点)的最小代价为0
dp[1] = 0; // 到达1级台阶(起点)的最小代价为0
// 3. 确定遍历顺序:从2到len正向遍历,依赖前两级的最小代价
for(int i = 2; i <= len; i++) {
// 4. 状态转移方程:
// 方式1:从i-1级爬1级上来,代价=dp[i-1]+cost[i-1]
// 方式2:从i-2级爬2级上来,代价=dp[i-2]+cost[i-2]
// 取两种方式中代价更小的作为dp[i]
dp[i] = Math.min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2]);
}
// 5. 返回到达楼顶(第len级)的最小总代价
return dp[len];
}
}