day32 代码随想录算法训练营 动态规划专题1

1 今日打卡

斐波那契数 509. 斐波那契数 - 力扣(LeetCode)

爬楼梯 70. 爬楼梯 - 力扣(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];
    }
}
相关推荐
JAVA面经实录9171 小时前
Netty 全套系统化学习文档(零基础到高阶面试完整版)
java·后端
菜鸟‍1 小时前
LeetCode 1 27 和 704 || 两数之和 移除元素 二分查找
算法·leetcode·职场和发展
weixin_523185321 小时前
Java面试高频题:Integer缓存机制与 equals、== 区别
java·缓存·面试
Hui Baby1 小时前
MCP SSE协议发送注意
java
仙俊红2 小时前
SpringBoot启动原理
java·spring boot·后端
星间都市山脉2 小时前
Android STS(Security Test Suite)完整介绍与测试流程
android·java·linux·windows·ubuntu·android studio·androidx
namexingyun2 小时前
拆解Fable 5三重安全护栏:模型路由、蒸馏防护与生物安全分类器的技术原理 - 微元算力(weytoken)
java·人工智能·python·安全·架构·ai编程
地铁潜行者2 小时前
加了幂等表,为什么消息重试反而不执行了?聊聊 MQ 消费幂等的边界
java·后端
退休倒计时2 小时前
【每日一题】LeetCode 142. 环形链表 II TypeScript
算法·leetcode·链表·typescript
popcorn_min3 小时前
Digits 手写数字识别:随机森林多分类 + 像素级特征热力图
算法·随机森林·分类