DAY42:动态规划(二)斐波那契数列+爬楼梯+最小花费爬楼梯

这篇具有很好参考价值的文章主要介绍了DAY42:动态规划(二)斐波那契数列+爬楼梯+最小花费爬楼梯。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

509.斐波那契数列

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

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

给定 n ,请计算 F(n) 。

示例 1:

输入:n = 2
输出:1
解释:F(2) = F(1) + F(0) = 1 + 0 = 1

示例 2:

输入:n = 3
输出:2
解释:F(3) = F(2) + F(1) = 1 + 1 = 2

示例 3:

输入:n = 4
输出:3
解释:F(4) = F(3) + F(2) = 2 + 1 = 3

提示:

  • 0 <= n <= 30

思路:动规五步

确定dp数组和数组下标含义

DP题目都需要定义一维或者二维的状态转移数组,通常是叫dp。

本题中,dp[i]表示第i个斐波那契数的数值为dp[i]

递推公式

本题是比较简单的DP题目,就是因为题目描述中已经把递推公式告诉我们了。

递推公式:dp[i]=dp[i-1]+dp[i-2]

DP数组初始化

题目描述已经说了,dp[0]=1

遍历顺序

因为dp[i]是由dp[i-1]和dp[i-2]得到的,因此需要从前往后遍历,才能保证每次dp[i]能够考虑到前面的两个元素。

打印DP数组

这一步主要用于debug,打印出来看看和想象的是否一样

完整版

class Solution {
public:
    int fib(int n) {
        if(n==0) 
            return 0;
        if(n==1) 
            return 1;
        //建立dp数组
        vector<int>dp(n+1);
        //dp数组初始化,初始化依赖于递推公式
        //注意这里初始化需要放到if特殊情况后面,因为如果n是0,就不存在dp[1]
        dp[0]=0;
        dp[1]=1;
        
        //递推公式
        for(int i=2;i<=n;i++){
            dp[i]=dp[i-1]+dp[i-2];
        }
        return dp[n];
    }
};
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)
debug测试

DAY42:动态规划(二)斐波那契数列+爬楼梯+最小花费爬楼梯,刷题记录,动态规划,算法,c++,leetcode,数据结构
这段代码的问题出在没有处理 n 为 0 或 1 的情况。如果 n 为 0,那么 dp[1] 就不存在,这时试图访问 dp[1] 会导致溢出

dp[0]=0;dp[1]=1;if特殊情况需要放最前面,因为如果n是0,就不存在dp[1]

修改加上if条件之后通过。

空间复杂度优化版

  • 实际上,我们只需要维护两个数值就可以了,不需要记录整个序列。
class Solution {
public:
    int fib(int N) {
        if (N <= 1) return N;
        int dp[2];
        dp[0] = 0;
        dp[1] = 1;
        for (int i = 2; i <= N; i++) {
            int sum = dp[0] + dp[1];
            dp[0] = dp[1];
            dp[1] = sum;
        }
        return dp[1];
    }
};
优化思路

斐波那契数列的定义:F(0) = 0,F(1) = 1,F(n) = F(n-1) + F(n-2) 对于所有 n >= 2。这意味着,要计算第 n 个斐波那契数,只需要知道前两个斐波那契数,即 F(n-1) 和 F(n-2)

优化版本的斐波那契数列计算利用了这个性质。在循环开始时,dp[0] 和 dp[1] 分别存储 F(n-2) 和 F(n-1)。然后,我们计算新的斐波那契数 F(n) = dp[0] + dp[1],并更新 dp[0] 和 dp[1],以备下一个循环使用。所以,我们只需要两个变量就可以计算出斐波那契数列的下一个值,而不必维护整个数列。

这样的优化实际上是一个空间优化,称为 “滚动数组” 或者 “滑动窗口” 的策略。其基本思想是只保存当前阶段需要的数据,淘汰过去不再需要的数据,避免存储不必要的信息,从而降低空间复杂度。

  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

70.爬楼梯

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

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

示例 1:

输入:n = 2
输出:2
解释:有两种方法可以爬到楼顶。
1. 1+ 12. 2

示例 2:

输入:n = 3
输出:3
解释:有三种方法可以爬到楼顶。

1. 1+ 1+ 12. 1+ 23. 2+ 1

提示:

  • 1 <= n <= 45

思路

一共n阶台阶,1阶:1步,2阶:2种(2或者1+1),3阶:3种(2+1或1+2或1+1+1)4阶:5种。

我们可以发现,3阶,只能从1阶和2阶迈上来,实际上就是1阶的1种方法加上2阶的2种方法

4阶,只能从2阶和3阶迈上来,因此登上4阶的方法数就是登上2阶的方法数+登上3阶的方法数,2+3=5种。

我们此时就可以发现递推关系,也就是当前阶梯的状态,依赖于他的前两个阶梯的状态。(一次性最多迈两步)

也就是说,因为每次只能爬 1 级或 2级,所以f(x)的数值只能从f(x-1)和f(x-2)转移过来。而这里要统计方案总数,我们就需要对这两项的贡献求和

DP数组的含义以及下标含义

dp[i]:达到第i阶,有dp[i]种方法。

后面的推导都是基于含义

递推公式

dp[i]=dp[i-1]+dp[i-2],其中dp[i-1]表示达到第i-1阶有多少种方法,dp[i-2]同理

DP数组初始化

首先因为题目描述达到第一阶有1种方法第二阶有2种,所以dp[1]=1,dp[2]=2.

dp[0]的含义是,达到第0阶需要多少种方法。但是本题中,dp[0]是没有意义的!因为题目给出的数据范围,n是一个>=1的正整数,因此我们完全不需要考虑dp[0]的情况,也不需要像题解一样令dp[0]=1,因为没有意义。

遍历顺序

遍历顺序一定是从前往后,因为本题也属于斐波那契数列题目,当前值基于他的前两个状态

打印DP数组

我们可以先推导自己认为的DP数组数值,然后打印看是否符合要求。

DAY42:动态规划(二)斐波那契数列+爬楼梯+最小花费爬楼梯,刷题记录,动态规划,算法,c++,leetcode,数据结构

完整版

  • 本题也是一道斐波那契数列的相关题目
class Solution {
public:
    int climbStairs(int n) {
        if(n<=2) return n;
        vector<int>dp(n+1,0);
        dp[1]=1;
        dp[2]=2;
        for(int i=3;i<=n;i++){
            dp[i]=dp[i-1]+dp[i-2];
        }
        return dp[n];
    }
};
debug测试

Line 1034: Char 34: runtime error: applying non-zero offset 4 to null pointer (stl_vector.h)

DAY42:动态规划(二)斐波那契数列+爬楼梯+最小花费爬楼梯,刷题记录,动态规划,算法,c++,leetcode,数据结构
这个错误信息是说试图对一个空的vector应用非零的偏移量。这个问题出在使用 dp[i] 之前没有为 dp 分配足够的空间

在C++中, std::vector 的初始大小为0,如果试图访问或修改不存在的元素(如 dp[1]dp[2]),这就会导致运行时错误

需要先调用 std::vector::resize 或者在创建 std::vector 时就指定它的大小,才能保证有足够的空间来存储元素。

修改dp初始化:vector<int>dp(n+1,0)

空间复杂度优化写法

  • 很多动规的题目其实都是当前状态依赖前两个,或者前三个状态,都可以做空间上的优化但面试中能写出版本一就够了,清晰明了,如果面试官要求进一步优化空间的话,我们再去优化
  • 因为版本一才能体现出动规的思想精髓,递推的状态变化、
// 版本二
class Solution {
public:
    int climbStairs(int n) {
        if (n <= 1) return n;
        int dp[3];
        dp[1] = 1;
        dp[2] = 2;
        for (int i = 3; i <= n; i++) {
            int sum = dp[1] + dp[2];
            dp[1] = dp[2];
            dp[2] = sum;
        }
        return dp[2];
    }
};

746.使用最小花费爬楼梯

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

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

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

示例 1:

输入:cost = [10,15,20]
输出:15
解释:你将从下标为 1 的台阶开始。

- 支付 15 ,向上爬两个台阶,到达楼梯顶部。
  总花费为 15

示例 2:

输入:cost = [1,100,1,1,1,100,1,1,100,1]
输出:6
解释:你将从下标为 0 的台阶开始。

- 支付 1 ,向上爬两个台阶,到达下标为 2 的台阶。
- 支付 1 ,向上爬两个台阶,到达下标为 4 的台阶。
- 支付 1 ,向上爬两个台阶,到达下标为 6 的台阶。
- 支付 1 ,向上爬一个台阶,到达下标为 7 的台阶。
- 支付 1 ,向上爬两个台阶,到达下标为 9 的台阶。
- 支付 1 ,向上爬一个台阶,到达楼梯顶部。
  总花费为 6

提示:

  • 2 <= cost.length <= 1000
  • 0 <= cost[i] <= 999

思路

本题首先要明确题意。题目中没有给出楼顶的位置,但是楼顶的阶数应该是cost.size()而不是cost数组的最大下标。题意如下图所示。

DAY42:动态规划(二)斐波那契数列+爬楼梯+最小花费爬楼梯,刷题记录,动态规划,算法,c++,leetcode,数据结构

可以选择0或者1往上跳,每次往上跳都花费cost[i]的体力。

DP数组含义

我们要求的是到达楼顶的最小花费,dp[i]表示的就是花费。

i表示的是当前到了哪个台阶,而dp[i]的值表示的就是到i位置时候的所有花费

DP数组含义一定要搞清楚,这一点很重要,递推公式基于数组

递推公式

递推公式我们需要得到的是dp[i]。本题可以一步一个台阶或者一步两个台阶,因此,dp[i]是由dp[i-1]或者dp[i-2]跳上来的

dp[i]表示的是,跳到i位置所需要的最小花费。因为既可以从i-1跳上来,也可以从i-2,因此递推就是取这二者花费的最小值。公式推导如下图:

DAY42:动态规划(二)斐波那契数列+爬楼梯+最小花费爬楼梯,刷题记录,动态规划,算法,c++,leetcode,数据结构
因此公式为,dp[i]=min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2])

DP数组初始化

dp公式可以看出,最开始的dp[2]是由dp[1]和dp[0]求得。也就是说我们只需要初始化dp[1]和dp[0]

因为0和1是初始值,往上跳的时候才需要花费体力值,因此dp[0]和dp[1]的值都是0.

(DP数组的含义:dp[i]表示的是跳到i时候的花费,初始值花费就是0)

遍历顺序

本题也是爬楼梯的衍生题目,因此也是从前到后遍历。

遍历顺序补充

但是稍稍有点难度的动态规划,其遍历顺序并不容易确定下来。 例如:01背包,都知道两个for循环,一个for遍历物品,嵌套一个for遍历背包容量,那么,为什么不是一个for遍历背包容量,嵌套一个for遍历物品呢? 以及在使用一维dp数组的时候遍历背包容量为什么要倒序呢

这些问题都是和遍历顺序有关的,等学到了背包再进行对比。

打印DP数组

debug过程中如果出现问题,就把预期DP数组写出,再打印进行对比。

预期DP数组:

DAY42:动态规划(二)斐波那契数列+爬楼梯+最小花费爬楼梯,刷题记录,动态规划,算法,c++,leetcode,数据结构文章来源地址https://www.toymoban.com/news/detail-542776.html

完整版

class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        if(cost.size()<=1) return 0;
        int n=cost.size();
        //初始化
        vector<int>dp(n+1,0);
        //dp[0]=0;已经进行了0的初始化这两句可以不写
        //dp[1]=0;
        for(int i=2;i<=n;i++){
            dp[i]=min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
        }
        return dp[n];
    }
};
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

空间复杂度优化写法

// 版本二
class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        int dp0 = 0;
        int dp1 = 0;
        for (int i = 2; i <= cost.size(); i++) {
            int dpi = min(dp1 + cost[i - 1], dp0 + cost[i - 2]);
            dp0 = dp1; // 记录一下前两位
            dp1 = dpi;
        }
        return dp1;
    }
};
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)
  • 在面试中,能写出版本一就行,除非面试官额外要求 空间复杂度,那么再去思考版本二,因为版本二还是有点绕。版本一才是正常思路。

到了这里,关于DAY42:动态规划(二)斐波那契数列+爬楼梯+最小花费爬楼梯的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 动态规划解决泰波那契数列,爬楼梯最小花费问题

    做题之前我们需要先搞清楚解决动态规划的几个步骤 1 状态表示,准备一个dp表 2 状态转移方程  3 初始化 4 填表 5 返回值 步骤1 状态表示,准备dp表 dp[0] dp[1] dp[2] dp[3] dp[4] = dp[0]+dp[1]+dp[3] 步骤2 状态转移方程表示 dp[i] = dp[i-1]+dp[i-2]+dp[i-3] 步骤3 4 5 都是对代码的细节处理,代码

    2024年02月03日
    浏览(29)
  • LeetCode刷题笔记【29】:动态规划专题-1(斐波那契数、爬楼梯、使用最小花费爬楼梯)

    动态规划(DP,Dynamic Programming)。 其解题思路对比 贪心算法的“直接选局部最优然后推导出全局最优” ;倾向于“ 由之前的结果推导得到后续的结果 ”。 很多时候二者具有相似性,不必死扣概念。 动态规划题目的核心是dp数组的概念和构建(递推公式); 所以具体的解题步骤

    2024年02月09日
    浏览(35)
  • 算法刷题Day 38 动态规划理论基础+斐波那契数+爬楼梯

    动态规划的解题步骤: 确定 dp 数组(dp table)以及下标的含义 确定递推公式 dp 数组如何初始化 确定遍历顺序 举例推导 dp 数组 很基础

    2024年02月15日
    浏览(37)
  • 算法训练第三十八天|动态规划理论基础、509. 斐波那契数 、70. 爬楼梯 、 746. 使用最小花费爬楼梯

    参考:https://programmercarl.com/%E5%8A%A8%E6%80%81%E8%A7%84%E5%88%92%E7%90%86%E8%AE%BA%E5%9F%BA%E7%A1%80.html 动态规划是什么 动态规划,英文:Dynamic Programming,简称DP,如果某一问题有很多重叠子问题,使用动态规划是最有效的。 所以 动态规划中每一个状态一定是由上一个状态推导出来的 ,这一

    2024年02月04日
    浏览(35)
  • 【动态规划】斐波那契数列模型

    冻龟算法系列之斐波那契数列模型 动态规划(英语:Dynamic programming,简称 DP) ,是一种在数学、管理科学、计算机科学、经济学和生物信息学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。动态规划常常适用于有重叠子问题和最优子结构性质

    2024年02月09日
    浏览(59)
  • 算法刷刷刷|动态规划篇|509.斐波那契数| 70.爬楼梯| 746.使用最小花费爬楼梯| 62.不同路径| 63不同路径2| 343.正数拆分 | 96.不同的二叉搜索树

    509. 斐波那契数 斐波那契数 (通常用 F(n) 表示)形成的序列称为 斐波那契数列 。该数列由 0 和 1 开始,后面的每一项数字都是前面两项数字的和。也就是: F(0) = 0,F(1) = 1 F(n) = F(n - 1) + F(n - 2),其中 n 1 给定 n ,请计算 F(n) 。 70.爬楼梯 746.使用最小花费爬楼梯 给你一个整数

    2023年04月23日
    浏览(52)
  • 【算法学习】斐波那契数列模型-动态规划

            我在算法学习过程中,针对斐波那契数列模型的动态规划的例题进行了一个整理,并且根据标准且可靠一点的动态规划解题思路进行求解类似的动归问题,来达到学习和今后复习的必要。         所谓的斐波那契数列模型,即当前状态的值等于前两种状态的值之和。

    2024年02月04日
    浏览(50)
  • 【算法优选】 动态规划之斐波那契数列模型

    动态规划相关题目都可以参考以下五个步骤进行解答: 状态表⽰ 状态转移⽅程 初始化 填表顺序 返回值 后面题的解答思路也将按照这五个步骤进行讲解。 泰波那契序列 Tn 定义如下: T0 = 0, T1 = 1, T2 = 1, 且在 n = 0 的条件下 Tn+3 = Tn + Tn+1 + Tn+2 给你整数 n,请返回第 n 个泰波那契

    2024年02月05日
    浏览(52)
  • C++算法 —— 动态规划(1)斐波那契数列模型

    每一种算法都最好看完第一篇再去找要看的博客,因为这样会帮你梳理好思路,看接下来的博客也就更轻松了。当然,我也会尽量在写每一篇时都可以让不懂这个算法的人也能边看边理解。 动规的思路有五个步骤,且最好画图来理解细节,不要怕麻烦。当你开始画图,仔细阅

    2024年02月10日
    浏览(41)
  • 【动态规划专栏】专题一:斐波那契数列模型--------1.第N个泰波那契数

    本专栏内容为:算法学习专栏,分为优选算法专栏,贪心算法专栏,动态规划专栏以及递归,搜索与回溯算法专栏四部分。 通过本专栏的深入学习,你可以了解并掌握算法。 💓博主csdn个人主页:小小unicorn ⏩专栏分类:动态规划专栏 🚚代码仓库:小小unicorn的代码仓库🚚

    2024年02月21日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包