算法训练营DAY38|509. 斐波那契数、70. 爬楼梯、746. 使用最小花费爬楼梯

本文介绍了动态规划算法的基本概念和应用,如解决股票交易、子序列、打家劫舍和背包问题。动态规划的核心是通过设置dp数组、找出递推公式、初始化、明确遍历顺序来解决问题。文章通过斐波那契数列和爬楼梯问题的实例,详细解释了解题步骤,并提供了简洁的代码实现。此外,还讨论了一种优化空间复杂度的方法。最后,提到了一个成本优化的爬楼梯问题,强调了初始条件和最小花费的计算策略。
摘要由CSDN通过智能技术生成

终于来到了动态规划,传说中的神奇算法,也是好多人闻声色变的一种难以被真正理解的算法。

同样的我们仍然采用循序渐进的由浅入深式的做题,来帮助我们更好的理解和接触动态规划。

首先动态规划算法,可以解决哪些类型的问题呢?

主要有买卖股票问题,子序列问题,打家劫舍问题,背包问题等问题,动态规划主要是用来解决某一问题可以由多个重叠的子问题组成时,优先选用动态规划,动态规划的每一个状态都是由上一个状态所推导出来的,是有理有据的。

解题套路

动态规划类题目都可以分解为五部曲

即:设置dp数组,找出递推公式,将dp数组部分初始化,明确遍历顺序,打表dp数组。

设置dp数组,要求我们明确dp数组在本题中的具体含义是什么,dp[i]是什么i又是什么。

递推公式通常是由题目已知量推导求得的,通常具有规律性的举例数字带入,可以帮助我们容易的确定递推公式。

将dp数组部分初始化,是根据题意的要求,将题目里已经给出的数据,初始化在dp数组中,这也和递推公式有关,一般要满足初始化的数据数量足够让递推公式推出下一个数据。

明确遍历顺序,就是要知道我们通过什么样的顺序,是从前到后还是从后向前的写一个循环,通过循环和递推公式在遍历顺序的作用下,填写dp数组。遍历顺序并不一定总是从前向后的。

最后一步,实际上是排错的,也就是题目无法ac时候,我们可以将dp数组最后的值全部打印出来,用来对比题中测试用例,用以排错。


了解了这些步骤后,相信大家才能够更好的学习动态递归的算法,即使是简单题也按照这一思路来思考,这样培养出感觉了之后,遇到困难题,才能有基本的思路。

509. 斐波那契数 - 力扣(LeetCode)https://leetcode.cn/problems/fibonacci-number/斐波那契数算是比较经典的题目了,相信大家在一开始学习递归的时候,就接触过这道题,递归思路也是十分简单,但是值得注意的是,当我们要求的第n个数字如果n非常大,那么递归是无法完成的,我们要选用更加高效的动态规划。

基本思路就是用数组dp来记载每个数的值,我们在填写第n个数字时,是前两个数字相加的和,这一个规律就是递推公式,这道题目已经将递推公式给出来了,所以我们不用找规律验证了。dp[i]代表了第i个斐波那契数,i就是代表第几个,我们初始化就是将第0个数字初始化为0第一个初始化为1,这都是题目里给出的,第0个由于没有数所以赋值0。遍历顺序很明显一定是从前向后,因为我们后一个数是前两个数和得到的。知道了这些,代码就不难写出来了。

class Solution {
public:
    int fib(int n) {
       if(n<=1)return n;
        int dp[n+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];
    }
};

我们还可以使代码更精简一些,由于我们只需要维护三个数字,第n个斐波那契数,第n-1个数和第n-2个数字,我们以这种思想来写题解的话,就可以不用数组来保存前面的数字了,使空间复杂度变成了O1。思路就是创立三个变量,来分别保存这些信息,最后返回,这里不给出代码了。


70. 爬楼梯 - 力扣(LeetCode)icon-default.png?t=MBR7https://leetcode.cn/problems/climbing-stairs/这道题是一道很适合入门的动态递归题目,问到n阶台阶共有几种走法,这道题没有做过的话,应该没有什么好的思路,看着很懵,但实际上和上一道题斐波那契数列差不多。

为什么这么说呢?我们每次只能走一步或者两步台阶,而走到第n阶的情况实际上可以等价于走到n-1阶后再走一阶达到目的地,也可以是走到n-2阶后再走两阶达到,那第一种我们可以理解,第二种n-2阶时候,可以走一阶再走一阶达到目的地吗?其实这样走的话,实际上就是n-1阶了,n-2走一步到n-1。所以我们走到第n阶思路就明确了,就是n-1阶的种数加上n-2阶种数就是走到第n阶总数,这一点和斐波那契数是一样的求法,实际上代码也是差不多的,唯一不同的就是dp数组的初始化部分,第1层就是1,第一层只有一种解法,而第二层有两种解法。

那有的同学要问了,为什么没有第0层了?实际上这道题有没有第0层影响都不大,由于是类似斐波那契数列解法,我们直接给出两个初始化能够求得剩余的就可以了,至于斐波那契那道题,0这个数也可以不赋值,采用1,2下标位置赋值成1也是可以的。

class Solution {
public:
    int climbStairs(int n) {
        if(n<=2)return n;
        int dp[n+1];
        dp[1]=1;dp[2]=2;
        for(int i=3;i<=n;i++)
        dp[i]=dp[i-1]+dp[i-2];//实际上是dp[i-1]再上一层楼梯和dp[i-2]再上两层楼梯,dp[i-2]如果只上一层楼梯那么和dp[i-1]就一样了,所以不算
        return dp[n];
    }
};

746. 使用最小花费爬楼梯 - 力扣(LeetCode)icon-default.png?t=MBR7https://leetcode.cn/problems/min-cost-climbing-stairs/这道题就相当于爬楼梯的消费版,虽然是这样说,但实际上还是有一些不一样的地方。

值得注意的点:我们一开始可以选择从0这个位置走,或者从1这个位置走,而且这两个开始的地方不收费,换句话说是往上爬楼梯时候收取的费用是现在所处的位置的价格,也就是上楼梯才收费,而且要注意我们要爬到顶端,是数组最后一个位置的下一个位置,而不是数组里包含元素-1。这一点很重要。

思路仍然是创建dp数组,dp[i]代表了达到这一层时候至少要多少钱,我们要求的是最小的花费。数组创建上创立一个数组数据个数+1这么大的空间,因为我们要求的是到楼顶的花费。数组初始化dp[0]=0dp[1]=0为什么这么写,上面也说过了,因为起始位置不花钱,往上走才花钱。

class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        vector<int> dp(cost.size()+1);
        dp[0]=0;dp[1]=0;
        for(int i=2;i<=cost.size();i++){
            dp[i]=min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
        }
        return dp[cost.size()];
    }
};

代码也是很简短的,dp[i]采用比较从哪一阶梯上来花的钱比较少,就存储哪一个方案。最后返回的钱数就一定是最少的开销。


以上代码均可ac。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

学习算法的杨

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值