代码随想录算法训练营第三十八天 | 509. 斐波那契数、70. 爬楼梯、746. 使用最小花费爬楼梯

前期基础:
(一)关键五步

  1. 确定dp数组(dp table)以及下标的含义
  2. 确定递推公式
  3. dp数组如何初始化
  4. 确定遍历顺序
  5. 举例推导dp数组

509. 斐波那契数

视频讲解

主要思路:

1.确定dp数组以及下标的含义

dp[i]的定义为:第i个数的斐波那契数值是dp[i]

2.确定递推公式

dp[i] = dp[i - 1] + dp[i - 2];

3.dp数组如何初始化

dp[0] = 0;
dp[1] = 1;

4.确定遍历顺序

从递归公式dp[i] = dp[i - 1] + dp[i - 2];中可以看出,dp[i]是依赖 dp[i - 1] 和 dp[i - 2],那么遍历的顺序一定是从前到后遍历的

5.举例推导dp数组

按照这个递推公式dp[i] = dp[i - 1] + dp[i - 2],我们来推导一下,当N为10的时候,dp数组应该是如下的数列:

0 1 1 2 3 5 8 13 21 34 55

代码实现:

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];
    }
};

第一次写错误 

还有可能出现只有一位的情况

70. 爬楼梯

视频讲解

主要思路:

与上一题菲波那切数列基本一致,关键在于发现递推公式

1.确定dp数组以及下标的含义

dp[i]: 爬到第i层楼梯,有dp[i]种方法

2.确定递推公式

如何可以推出dp[i]呢?

从dp[i]的定义可以看出,dp[i] 可以有两个方向推出来。

首先是dp[i - 1],上i-1层楼梯,有dp[i - 1]种方法,那么再一步跳一个台阶不就是dp[i]了么。

还有就是dp[i - 2],上i-2层楼梯,有dp[i - 2]种方法,那么再一步跳两个台阶不就是dp[i]了么。

那么dp[i]就是 dp[i - 1]与dp[i - 2]之和!

所以dp[i] = dp[i - 1] + dp[i - 2] 。

在推导dp[i]的时候,一定要时刻想着dp[i]的定义,否则容易跑偏。

这体现出确定dp数组以及下标的含义的重要性!

3.dp数组如何初始化

在回顾一下dp[i]的定义:爬到第i层楼梯,有dp[i]中方法。

那么i为0,dp[i]应该是多少呢,这个可以有很多解释,但基本都是直接奔着答案去解释的。

例如强行安慰自己爬到第0层,也有一种方法,什么都不做也就是一种方法即:dp[0] = 1,相当于直接站在楼顶。

但总有点牵强的成分。

那还这么理解呢:我就认为跑到第0层,方法就是0啊,一步只能走一个台阶或者两个台阶,然而楼层是0,直接站楼顶上了,就是不用方法,dp[0]就应该是0.

其实这么争论下去没有意义,大部分解释说dp[0]应该为1的理由其实是因为dp[0]=1的话在递推的过程中i从2开始遍历本题就能过,然后就往结果上靠去解释dp[0] = 1

从dp数组定义的角度上来说,dp[0] = 0 也能说得通。

需要注意的是:题目中说了n是一个正整数,题目根本就没说n有为0的情况。

所以本题其实就不应该讨论dp[0]的初始化!

所以:不考虑dp[0]如何初始化,只初始化dp[1] = 1,dp[2] = 2,然后从i = 3开始递推,这样才符合dp[i]的定义。

4.确定遍历顺序

从递推公式dp[i] = dp[i - 1] + dp[i - 2];中可以看出,遍历顺序一定是从前向后遍历的

5.举例推导dp数组

代码实现:

class Solution {
public:
    int climbStairs(int n) {
        if(n <= 2) return n;
        int dp[2];  //dp[i]表示爬到第i阶台阶有几种方法,不过这里与斐波那契数列一样,只用维护两个就行
        dp[0] = 1;  
        dp[1] = 2;
        for(int i = 3; i <= n; i++) {
            int sum = dp[0] + dp[1];
            dp[0] = dp[1];
            dp[1] = sum;
        }
        return dp[1];
    }
};

746. 使用最小花费爬楼梯

视频讲解

主要思路:

(1)dp数组及下标含义:跳到第i个楼梯所需最小体力值

(2)递推公式:因为一次能跳1或2个台阶,所以dp[i] = min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]

(3)初始化:可以从第0个或第1个台阶开始跳,所以初始化dp[0]与dp[1]为0

(4)遍历顺序:从前往后

易错点:

(1)i表示台阶序号,最后顶楼的序号是cost.size()

代码实现:

class Solution {
public:
    int minCostClimbingStairs(vector& cost) {
        if(cost.size() <= 1) return 0;
        int dp[2];  //dp数组中记录的是跳到当前位置所需的最小体力值
        //空间上的优化,因为只用记录要跳到当前位置前两个位置即可
        dp[0] = 0;  
        dp[1] = 0;
        for(int i = 2; i <= cost.size(); i++) { //i <= cost.size()是因为i表示跳到i这个位置,最后的顶楼位置下标也就是cost数组长度
            int next = min(dp[0] +cost[i - 2], dp[1] + cost[i - 1]);
            dp[0] = dp[1];
            dp[1] = next;
        }
        return dp[1];
    }
};

第一次写错误 

(1) cost数组大小为1时才直接返回0

你可能感兴趣的:(算法训练营,算法,动态规划)