代码随想录算法训练营 Day28 | 动态规划 part01

509. 斐波那契数

斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 01 开始,后面的每一项数字都是前面两项数字的和。也就是:

F(0) = 0,F(1) = 1

F(n) = F(n - 1) + F(n - 2),其中 n > 1

给定 n ,请计算 F(n)

cpp 复制代码
class Solution {
public:
    int fib(int n) {
        if(n < 2) return n;       // 1. 特殊情况处理
        vector<int> dp(n + 1);    // 2. 创建 DP 表(状态数组)
        dp[0] = 0;                // 3. 初始化状态
        dp[1] = 1;
        for(int i = 2; i <= n; i++){ // 4. 状态转移(填表)
            dp[i] = dp[i-1] + dp[i-2]; // 递推公式
        }
        return dp[n];             // 5. 返回结果
    }
};

总结

1. 解题五步法分析
  • 确定 dp 数组含义:dp[i] 定义为第 i 个斐波那契数的值。
  • 确定递推公式:题目直接给了,dp[i] = dp[i-1] + dp[i-2]。这是 DP 的灵魂。
  • dp 数组初始化:dp[0]=0, dp[1]=1。这是递推的基石。
  • 确定遍历顺序:因为 dp[i] 依赖前面的 dp[i-1]dp[i-2],所以必须 从前向后 遍历。
  • 举例推导:比如 n=4,序列是 0, 1, 1, 2, 3。手动推导验证代码逻辑是否正确。
2. 复杂度分析
  • 时间复杂度:O(n)。只有一个循环,执行 n−1次。
  • 空间复杂度:O(n)。开辟了一个长度为 n+1的数组。

70. 爬楼梯

假设你正在爬楼梯。需要 n 阶你才能到达楼顶。

每次你可以爬 12 个台阶。你有多少种不同的方法可以爬到楼顶呢?

cpp 复制代码
class Solution {
public:
    int climbStairs(int n) {
        if (n <= 2) return n; 
        // 1. 定义 DP 数组
        // dp[i] 表示爬到第 i 阶楼梯的方法数
        vector<int> dp(n + 1);
        // 2. 初始化状态
        // dp[0] 本题无意义,从 dp[1] 开始
        dp[1] = 1; // 1阶楼梯:1种方法 (1)
        dp[2] = 2; // 2阶楼梯:2种方法 (1+1, 2)
        // 3. 状态转移(填表)
        // 从第 3 阶开始遍历
        for (int i = 3; i <= n; i++) {
            // 递推公式:
            // 要么是从第 i-1 阶迈 1 步上来的
            // 要么是从第 i-2 阶迈 2 步上来的
            dp[i] = dp[i - 1] + dp[i - 2];
        }
        return dp[n];
    }
};

总结

1. 思维推导
  • 最后一步:想要跳到第 n 阶,最后一步只有两种选择:
    • n-1 阶跳 1 步。
    • n-2 阶跳 2 步。
  • 推导:既然最后一步只有这两种走法,那么走到 n 阶的方法数 = 走到 n-1 的方法数 + 走到 n-2 的方法数。
  • 结论:这就是斐波那契数列的递推公式。
2. 复杂度分析
  • 时间复杂度:O(n),一次循环。
  • 空间复杂度:O(n),用数组存储了所有状态。

746. 使用最小花费爬楼梯

给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。

你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。

请你计算并返回达到楼梯顶部的最低花费。

cpp 复制代码
class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        int n = cost.size();
        // 1. 定义 DP 数组
        // dp[i] 表示:到达第 i 个台阶顶端(即站在第 i 个位置)所需要的最小花费
        // 大小为 n+1,因为我们要到达的是数组末尾的"楼顶"(下标 n)
        vector<int> dp(n + 1, 0);
        // 2. 初始化
        // 题目说可以从下标 0 或 1 开始,意味着站在起点是不花钱的
        // dp[0] = 0, dp[1] = 0 (vector初始化时已设为0)
        // 3. 状态转移
        // 从第 2 层开始遍历,直到楼顶 n
        for (int i = 2; i <= n; i++) {
            // 想要到达第 i 层,只有两种来源:
            // 1. 从第 i-1 层迈 1 步上来(花费:到达 i-1 的最小花费 + 踩 i-1 的费用)
            // 2. 从第 i-2 层迈 2 步上来(花费:到达 i-2 的最小花费 + 踩 i-2 的费用)
            dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
        }
        return dp[n];
    }
};

总结

1. 核心思路对比
  • 普通爬楼梯:dp[i] 存的是方法数(求和)。
  • 最小花费爬楼梯:dp[i] 存的是最小花费(求 Min)。
2. 两种思维模型
  • 本题的定义:dp[i] 代表 到达 第 i 级台阶的最小花费。

    • 起点的花费为 0。
    • 花费产生在"迈步"的过程中(dp[i-1] + cost[i-1])。
    • 这种思路符合题目"支付费用才能向上爬"的描述,逻辑顺畅。
  • 另一种定义:dp[i] 代表 经过 第 i 级台阶时的最小花费(包含踩上去的钱)。

    • 那么初始化就是 dp[0]=cost[0], dp[1]=cost[1]
    • 递推公式是 dp[i] = min(dp[i-1], dp[i-2]) + cost[i]
    • 最后结果是 min(dp[n-1], dp[n-2])
    • 这种写法容易在初始化和最后返回值上纠结。
3. 复杂度分析
  • 时间复杂度:O(n),一次遍历。
  • 空间复杂度:O(n),使用了长度为 n+1的数组。
相关推荐
变量未定义~1 小时前
最小生成树1(Prim模板)、最小生成树2(kruskal模板)、最近公共祖先(模板)
算法
这就是佬们吗1 小时前
Python入门⑤-异常处理、文件操作与实战项目
开发语言·数据库·python·算法·pycharm
LONGZETECH1 小时前
新能源汽车充电设备装配与调试仿真教学软件 技术架构与核心实现解析
大数据·算法·3d·unity·架构·汽车
自学小白菜1 小时前
关于提升机器学习算法做预测回归任务精度的方法分享
算法·机器学习·回归·提升精度
学废了wuwu1 小时前
从入门到实战:深入浅出模型压缩三大核心——剪枝、彩票假设与知识蒸馏
人工智能·算法·剪枝
小肝一下1 小时前
3. 单链表
c语言·数据结构·c++·算法·leetcode·链表·dijkstra
劈星斩月1 小时前
监督学习算法 之 决策树
学习·算法·决策树
学计算机的计算基2 小时前
操作系统内存管理全解:虚拟内存、页表、COW、malloc、OOM一篇搞定
java·笔记·算法
tachibana22 小时前
hot100 前 K 个高频元素(347)
java·数据结构·算法·leetcode
陈天伟教授2 小时前
暴力美学-大型语言模型与涌现:一个复杂系统视角
人工智能·算法