代码随想录算法训练营 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的数组。
相关推荐
光电笑映2 小时前
STL 源码解密:unordered 系列容器的底层复用与哈希策略
算法·哈希算法·散列表
6Hzlia2 小时前
【Hot 100 刷题计划】 LeetCode 215. 数组中的第K个最大元素 | C++ 快速选择与堆排序题解
c++·算法·leetcode
小白菜又菜2 小时前
Leetcode 3070. Count Submatrices with Top-Left Element and Sum Less Than k
算法·leetcode·职场和发展
笨笨饿3 小时前
32_复变函数在工程中实际应用区别于联系
linux·服务器·c语言·人工智能·单片机·算法·学习方法
会编程的土豆3 小时前
【数据结构与算法】拓扑排序2
数据结构·算法·leetcode
Boop_wu3 小时前
[Java 算法] 栈
java·开发语言·算法
追风落叶乔木生3 小时前
字节跳动后端一面全解析|基础+算法真题(2026最新版)
算法·哈希算法
来自远方的老作者3 小时前
第7章 运算符-7.5 比较运算符
开发语言·数据结构·python·算法·代码规范·比较运算符
We་ct3 小时前
LeetCode 201. 数字范围按位与:位运算高效解题指南
开发语言·前端·javascript·算法·leetcode·typescript