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];
    }
}
相关推荐
爱刷碗的苏泓舒1 小时前
PPP-AR 中的参考星选取:数学原理、评价指标与切换处理
算法·gnss·模糊度固定·ppp-ar·星间单差·参考星·卫星端偏差
维天说2 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json
Zane19943 小时前
并发 vs 并行:别再傻傻分不清了,一文讲透 Java 并发编程的第一课
java·后端
噢,我明白了3 小时前
java中Excel的导入和导出(EasyExcel)
java·开发语言·excel
吠品3 小时前
Zabbix Web界面误报Server未运行的排查与解决
java·服务器·数据库
啊湘4 小时前
天气查询API接口 按月Token鉴权 实时天气 物联网可用 文档齐全
java·后端·struts
Java内核笔记4 小时前
告别十亿美元的错误 : Spring Boot 4 空安全 (JSpecify) 实战
java·spring boot·后端
烬羽4 小时前
递归老写崩?一个"退回"公式,把回溯题变成填空题
javascript·深度学习·算法
糖果店的幽灵4 小时前
langgraph的 MessagesState 解读
java·开发语言·人工智能·windows·langgraph
闪电悠米4 小时前
力扣hot100-41.缺失的第一个正数-原地哈希详解
数据结构·算法·哈希算法