代码随想录算法训练营 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的数组。
相关推荐
Chase_______4 分钟前
LeetCode 1343 题解:定长滑动窗口经典入门题,从暴力枚举到高效优化一文搞懂
算法·leetcode·职场和发展
样例过了就是过了5 分钟前
LeetCode热题100 单词拆分
c++·算法·leetcode·动态规划·哈希算法
王老师青少年编程19 分钟前
csp信奥赛C++高频考点专项训练之贪心算法 --【跳跃与过河问题】:跳跳!
c++·算法·贪心·csp·信奥赛·跳跃与过河问题·跳跳
MediaTea20 分钟前
ML:决策树的基本原理与实现
人工智能·算法·决策树·机器学习·数据挖掘
王老师青少年编程21 分钟前
csp信奥赛C++高频考点专项训练之贪心算法 --【跳跃与过河问题】:独木桥
c++·算法·贪心·csp·信奥赛·跳跃与过河问题·独木桥
忡黑梨30 分钟前
eNSP_DHCP配置
c语言·网络·c++·python·算法·网络安全·智能路由器
陈壮实的搬砖日记43 分钟前
白话生成式推荐二:MiniOneRec之RQ-VAE
算法
陈壮实的搬砖日记1 小时前
白话生成式推荐二:MiniOneRec之SFT
算法
她说彩礼65万1 小时前
C语言 动态内存管理
c语言·开发语言·算法
Irene19911 小时前
数据排序为什么默认升序
算法·排序