【算法】动态规划-斐波那契数列模型

目录

1、第N个泰波那契数

1.1 算法原理讲解

1.1.1 状态表示

1.1.2 状态转移方程

1.1.3 初始化

1.1.4 填表顺序

1.1.5 返回值

1.2 代码实现

1.3 空间优化

2、三步问题

2.1 算法原理讲解

2.1.1 状态表示

2.1.2 状态转移方程

2.1.3 初始化

2.1.4 填表顺序

2.1.5 返回值

2.2 代码实现

3、使用最小花费爬楼梯

3.1 解法一

3.1.1 状态表示

3.1.2 状态转移方程

3.1.3 初始化

3.1.4 填表顺序

3.1.5 返回值

3.1.6 代码实现

3.2 解法二

3.2.1 状态表示

3.2.2 状态转移方程

3.2.3 初始化

3.2.4 填表顺序

3.2.5 返回值

3.2.6 代码实现

4、解码方法


1、第N个泰波那契数

1.1 算法原理讲解

动态规划就是创建1个一维数组或者二维数组作为DP表,填DP表,DP表中的某一项就是结果

动态规划的步骤通常分为5步

1.1.1 状态表示

是什么? dp表中每一项所代表的含义

怎么来? 1. 题目要求

               2. 经验 + 题目要求

               3. 分析问题的过程中,发现重复子问题 

这道题是需要返回第n个泰斐波那契数列的值,所以dp[i]就表示第i个泰斐波那契数列的值

1.1.2 状态转移方程

动态规划的最终结果就是要将dp表填满,所以应该要知道dp[i]等于什么,即用之前的状态或之后的状态来表示当前的状态

状态转移方程就是dp[i]等于什么

这道题中dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3]

1.1.3 初始化

初始化是为了在填表的时候不会越界

这道题n是从0开始的,那么dp[0],dp[1],dp[2],放入状态转移方程中都会造成数值越界,所以需要对这几个值进行初始化

由题意可知,dp[0] = 0,dp[1] = dp[2] = 1

1.1.4 填表顺序

确定是从左向右开始填,还是从右向左开始填

依据就是,为了填写当前状态,所需要的状态已经计算过了

像这道题,填写当前状态,需要它的前三个状态已经填写过,所以是从左向右填

1.1.5 返回值

题目要求 + 状态表示

这道题就是dp[n]

1.2 代码实现

class Solution {
public:
    int tribonacci(int n) {
        if(n == 0) return 0;
        if(n == 1 || n == 2) return 1;
        vector<int> dp(n + 1);
        dp[0] = 0,dp[1] = dp[2] = 1;
        for(int i = 3;i<=n;i++)
        {
            dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3];
        }
        return dp[n];
    }
};

1.3 空间优化

这道题中,当依次向后求dp[i]时,前面的有些状态是可以舍弃的,所以可以利用滚动数组优化

优化可以将空间复杂度O(N^2) -> O(N) , O(N) -> O(1)

class Solution {
public:
    int tribonacci(int n) {
        if(n == 0) return 0;
        if(n == 1 || n == 2) return 1;
        int a = 0,b = 1,c = 1,d = 0;
        for(int i = 3;i<=n;i++)
        {
            d = a + b + c;
            a = b;b = c;c = d;
        }
        return d;
    }
};

2、三步问题

2.1 算法原理讲解

2.1.1 状态表示

dp[i]表示到达i位置一共有多少种方法

2.1.2 状态转移方程

第n个台阶可以从第n-1个台阶跨1步上去,也可以从第n-2个台阶跨2步上去,还可以从第n-3个台阶跨3步上去,所以dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3]

2.1.3 初始化

这道题n是从1开始的,那么dp[1],dp[2],dp[3],放入状态转移方程中都会造成数值越界,所以需要对这几个值进行初始化

2.1.4 填表顺序

这道题,填写当前状态,需要它的前三个状态已经填写过,所以是从左向右填

2.1.5 返回值

返回dp[n]

2.2 代码实现

class Solution {
public:
    int waysToStep(int n) {
        const int MOD = 1000000007;// 即le9 + 7

        //处理边界条件
        if(n == 1 || n == 2) return n;
        if(n == 3) return 4;
        vector<int> dp(n + 1);
        dp[1] = 1,dp[2] = 2,dp[3] = 4;
        for(int i = 4;i<=n;i++)
            dp[i] = ((dp[i - 1] + dp[i - 2]) % MOD + dp[i - 3]) % MOD;
            //注意这里每加一个就要取模,否则可能前两个相加就超过了int的最大范围
        return dp[n];
    }
};

注意:题目中说结果可能很大,所以每加1次就要取一次模

3、使用最小花费爬楼梯

3.1 解法一

3.1.1 状态表示

dp[i]表示到达第i阶台阶所需要的最小花费

3.1.2 状态转移方程

第i阶台阶是从第i - 1阶或第i - 2阶台阶上来的,所以到达第i阶台阶所需要的最小花费就等于到达i-1阶台阶的最小花费加上i-1阶台阶的花费、i-2阶台阶的最小花费加上i - 2阶台阶的花费,这二者中的较小值

dp[i] = min(dp[i - 1] + cost[i - 1] , dp[i - 2] + cost[i - 2])

3.1.3 初始化

需要对dp[0]和dp[1]初始化,因为可以选择从下标为 0 或 1 的元素作为初始阶梯

所以,dp[0] = dp[1] = 0

3.1.4 填表顺序

从左向右

3.1.5 返回值

dp[n]

3.1.6 代码实现
class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        // 解法一:dp表的第i项表示到第i阶台阶所需要的体力值
        int n = cost.size();
        vector<int> dp(n + 1);// dp表的第i项表示到第i阶台阶所需要的体力值
        dp[0] = dp[1] = 0;// 因为可以选择从第1阶或第2阶开始走
        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];
    }
};

3.2 解法二

3.2.1 状态表示

dp[i]表示从第i项到顶部所需要的最小花费

3.2.2 状态转移方程

从第i阶台阶可以向上走1步或者2步,所以从第i阶台阶到顶部的最小花费就等于这一步台阶的花费,加上从第i + 1阶台阶到顶部的最小花费或从第i + 2阶台阶到顶部的最小花费中的较小值

dp[i] = min(dp[i - 1] , dp[i - 2]) + cost[i]

3.2.3 初始化

需要对dp[n - 1]和dp[n - 2]初始化

dp[n - 1] = cost[n - 1],dp[n - 2] = cost[n - 2]

3.2.4 填表顺序

从右向左

3.2.5 返回值

返回dp[0]或dp[1]中的较小值,因为可以选择从下标为 0 或 1 的元素作为初始阶梯

3.2.6 代码实现
class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        // 解法二:dp表的第i项表示从第i阶台阶到顶层所需要的体力值
        int n = cost.size();
        vector<int> dp(n);
        dp[n - 1] = cost[n - 1],dp[n - 2] = cost[n - 2];
        for(int i = n - 3;i>=0;i--)
        {
            dp[i] = min(dp[i + 1],dp[i + 2]) + cost[i];
        }
        return min(dp[0],dp[1]);
    }
};

4、解码方法

dp[i]表示当前位置有几种解码方法

根据题目的意思,0不能单独组成一个编码,也不能在与其他字符组成编码时位于首位

此时需要分两种情况讨论:

1. s[i]可以自身解码,即s[i] != '0'

若s[i]还能和s[i - 1]共同解码,即s[i]和s[i - 1]组成的数字范围在[10,26],dp[i] = dp[i - 1] + dp[i - 2]

若s[i]不能和s[i - 1]共同解码,dp[i] = dp[i - 1]

2. s[i]不能自身解码,即s[i] == '0'

若s[i]还能和s[i - 1]共同解码,即s[i]和s[i - 1]组成的数字范围在[10,26],dp[i] = dp[i - 2]

若s[i]不能和s[i - 1]共同解码,dp[i] = 0

通过上面可知,为了防止溢出,我们需要对dp[0]和dp[1]初始化

1. 当s[0] == '0',dp[0] = dp[1] = 0

2. 当s[0] != '0',dp[0] = 1,若dp[1]无法自身解码,也无法和dp[0]共同解码,则dp[1] = 0,若dp[1]可以自身解码,也可以和dp[0]共同解码,则dp[1] = 2,若dp[1]可以自身解码和dp[1]可以于dp[0]共同解码只有一个为真,则dp[1] = 1

返回值就是dp[n - 1]

class Solution {
public:
    int numDecodings(string s) {
        int n = s.size();
        if(n == 1) return s[0] == '0'? 0 : 1;
        vector<int> dp(n);//dp是记录当前位置一共有几种解码方法
        if(s[0] == '0')// s[0]无法自身解码
        {
            dp[0] = dp[1] = 0;// 当s[0] == '0',则下标1处的解码方法数为0,因为0后面加一个数是解码失败的
        }
        else// s[0]可以自身解码
        {
            dp[0] = 1;
            int x = (s[0] - '0') * 10 + s[1] - '0';// 记录s[0] 和 s[1]组成的数字是多少
            if(s[1] == '0' && x >27) dp[1] = 0;// s[1]无法自身解码,也无法与s[0]共同解码
            else if(s[1] != '0' && x >= 10 && x <= 26) dp[1] = 2;//s[1]可以自身解码,也可与s[0]共同解码
            else dp[1] = 1;// s[1]只能自身解码或者与s[0]共同解码
        }
        for(int i = 2;i<n;i++)
        {
            if(s[i] == '0')// 自身无法解码
            {
                int x = (s[i - 1] - '0') * 10 + s[i] - '0';
                if(x >= 10 && x <= 26) dp[i] = dp[i - 2];// 可以与前一个共同解码
                else dp[i] = 0;// 无法与前一个共同解码
            }
            else// 自身可以解码
            {
                int x = (s[i - 1] - '0') * 10 + s[i] - '0';
                if(x >= 10 && x <= 26) dp[i] = dp[i -1] + dp[i - 2];// 可以与前一个共同解码
                else dp[i] = dp[i - 1];// 无法与前一个共同解码
            }
        }
        return dp[n - 1];
    }
};
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值