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

1 今日打卡

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

爬楼梯 70. 爬楼梯 - 力扣(LeetCode)

使用最小花费爬楼梯 746. 使用最小花费爬楼梯 - 力扣(LeetCode)

2 dp基础理论

dp五部曲:

确定dp数组(dp table)以及下标的含义

确定递推公式

dp数组如何初始化

确定遍历顺序

举例推导dp数组

3 斐波那契数

3.1 思路

第一步:

dp[i] 表示斐波那契数列的第 i 项的值;

下标i 对应斐波那契数列的项数(从 0 开始),比如dp[0]是第 0 项,dp[1]是第 1 项,dp[2]是第 2 项,以此类推。

第二步:

标准形式:dp[i] = dp[i-1] + dp[i-2](i ≥ 2),入门题目。因为直接给出了转移方程

第三步:

第 0 项:dp[0] = 0;

第 1 项:dp[1] = 1

第四步:

状态转移方程中,dp[i]依赖dp[i-1]和dp[i-2](前两项),因此必须从前往后遍历(从 2 到 n),才能保证计算当前项时,前两项已经有正确的值。

第五步:

以n=5为例,手动推导代码的执行过程,验证逻辑正确性:

初始状态:dp[0] = 0,dp[1] = 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 项);

最终返回dp[1] = 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 思路

第一步:

dp[i] 表示:爬到第 i 级台阶时,一共有多少种不同的方法;

第二步:

要爬到第i级台阶,最后一步只有两种可能:

从第i-1级台阶爬 1 级上来;

从第i-2级台阶爬 2 级上来。

状态转移方程:dp[i] = dp[i-1] + dp[i-2](i ≥ 2),即爬到第i级的方法数 = 爬到i-1级的方法数 + 爬到i-2级的方法数。

第三步:

dp[0] = 1:爬到第 0 级台阶(地面)只有 1 种方法(原地不动),这是为了让状态转移方程在i=2时成立(dp[2] = dp[1] + dp[0] = 1 + 1 = 2,符合实际:爬 2 级台阶有 "1+1""2" 两种方法);

dp[1] = 1:爬到第 1 级台阶只有 1 种方法(直接爬 1 级);

第四步:

状态转移方程中,dp[i]依赖dp[i-1]和dp[i-2](前两级的结果),因此必须从前往后遍历(从 2 到 n),保证计算dp[i]时,dp[i-1]和dp[i-2]已经有正确的值。

第五步:

以n=5为例,手动推导验证逻辑正确性:

初始状态:dp[0] = 1,dp[1] = 1;

i=2:dp[2] = dp[1] + dp[0] = 1 + 1 = 2(方法:1+1、2);

i=3:dp[3] = dp[2] + dp[1] = 2 + 1 = 3(方法:1+1+1、1+2、2+1);

i=4:dp[4] = dp[3] + dp[2] = 3 + 2 = 5;

i=5:dp[5] = dp[4] + dp[3] = 5 + 3 = 8;

最终dp[5] = 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 的含义

dp[i] 表示:到达第 i 级台阶(准备向上爬)时,所需要花费的最小总代价;

第二步:

到达第i级台阶有两种途径:

从第i-1级台阶爬 1 级上来:总代价 = 到达i-1级的最小代价 + 爬i-1级的代价 → dp[i-1] + cost[i-1];

从第i-2级台阶爬 2 级上来:总代价 = 到达i-2级的最小代价 + 爬i-2级的代价 → dp[i-2] + cost[i-2];

状态转移方程:dp[i] = Math.min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2])(i ≥ 2),即取两种途径中代价更小的那个。

第三步:

dp[0] = 0:到达第 0 级台阶(起点),不需要花费任何代价;

dp[1] = 0:到达第 1 级台阶(起点),也不需要花费任何代价;

这两个初始化值是题目规则的直接体现,也是后续计算的基础。

第四步:

状态转移方程中,dp[i]依赖dp[i-1]和dp[i-2](前两级的最小代价),因此必须从前往后遍历(从 2 到len),保证计算dp[i]时,前两级的结果已经是确定的最小值。代码中的遍历逻辑:for(int i = 2; i <= len; i++),从第 2 级台阶开始,一直计算到楼顶(len级)。

第五步:

以cost = [10, 15, 20]为例(台阶数len=3,目标爬到第 3 级即楼顶),手动推导验证:

初始状态:dp[0] = 0,dp[1] = 0;

i=2(到达第 2 级台阶):

dp[2] = min(dp[1]+cost[1], dp[0]+cost[0]) = min(0+15, 0+10) = 10;

i=3(到达楼顶):

dp[3] = min(dp[2]+cost[2], dp[1]+cost[1]) = min(10+20, 0+15) = 15;

最终返回dp[3] = 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];
    }
}
相关推荐
我能坚持多久2 小时前
【初阶数据结构03】——单链表专题
数据结构
啊阿狸不会拉杆2 小时前
《机器学习导论》第 19 章 - 机器学习实验的设计与分析
人工智能·python·算法·决策树·机器学习·统计检验·评估方法
Forget_85502 小时前
RHEL——web应用服务器TOMCAT
java·前端·tomcat
革凡成圣2112 小时前
回忆大一[特殊字符]
数据结构
v沙加v2 小时前
Java Rendering Engine Unknown
java·开发语言
一马平川的大草原2 小时前
读书笔记--秒懂算法:用常识解读数据结构与算法阅读与记录
数据结构·算法·大o
你撅嘴真丑2 小时前
第九章-训练参考
算法
烟花落o2 小时前
【数据结构系列01】时间复杂度和空间复杂度:消失的数字
数据结构·算法·leetcode·刷题
㓗冽2 小时前
阵列(二维数组)-基础题79th + 饲料调配(二维数组)-基础题80th + 求小数位数个数(字符串)-基础题81th
数据结构·c++·算法