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

目录

1、第N个泰波那契数

[1.1 算法原理讲解](#1.1 算法原理讲解)

[1.1.1 状态表示](#1.1.1 状态表示)

[1.1.2 状态转移方程](#1.1.2 状态转移方程)

[1.1.3 初始化](#1.1.3 初始化)

[1.1.4 填表顺序](#1.1.4 填表顺序)

[1.1.5 返回值](#1.1.5 返回值)

[1.2 代码实现](#1.2 代码实现)

[1.3 空间优化](#1.3 空间优化)

2、三步问题

[2.1 算法原理讲解](#2.1 算法原理讲解)

[2.1.1 状态表示](#2.1.1 状态表示)

[2.1.2 状态转移方程](#2.1.2 状态转移方程)

[2.1.3 初始化](#2.1.3 初始化)

[2.1.4 填表顺序](#2.1.4 填表顺序)

[2.1.5 返回值](#2.1.5 返回值)

[2.2 代码实现](#2.2 代码实现)

3、使用最小花费爬楼梯

[3.1 解法一](#3.1 解法一)

[3.1.1 状态表示](#3.1.1 状态表示)

[3.1.2 状态转移方程](#3.1.2 状态转移方程)

[3.1.3 初始化](#3.1.3 初始化)

[3.1.4 填表顺序](#3.1.4 填表顺序)

[3.1.5 返回值](#3.1.5 返回值)

[3.1.6 代码实现](#3.1.6 代码实现)

[3.2 解法二](#3.2 解法二)

[3.2.1 状态表示](#3.2.1 状态表示)

[3.2.2 状态转移方程](#3.2.2 状态转移方程)

[3.2.3 初始化](#3.2.3 初始化)

[3.2.4 填表顺序](#3.2.4 填表顺序)

[3.2.5 返回值](#3.2.5 返回值)

[3.2.6 代码实现](#3.2.6 代码实现)

4、解码方法


1、第N个泰波那契数

1.1 算法原理讲解

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

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

1.1.1 状态表示

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

怎么来? 1. 题目要求

  1. 经验 + 题目要求

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

这道题是需要返回第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]

  1. 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];
    }
};
相关推荐
engchina14 分钟前
如何在 Python 中忽略烦人的警告?
开发语言·人工智能·python
向宇it15 分钟前
【从零开始入门unity游戏开发之——C#篇24】C#面向对象继承——万物之父(object)、装箱和拆箱、sealed 密封类
java·开发语言·unity·c#·游戏引擎
诚丞成40 分钟前
计算世界之安生:C++继承的文水和智慧(上)
开发语言·c++
清梦202044 分钟前
经典问题---跳跃游戏II(贪心算法)
算法·游戏·贪心算法
Smile灬凉城6661 小时前
反序列化为啥可以利用加号绕过php正则匹配
开发语言·php
lsx2024061 小时前
SQL MID()
开发语言
Dream_Snowar1 小时前
速通Python 第四节——函数
开发语言·python·算法
西猫雷婶1 小时前
python学opencv|读取图像(十四)BGR图像和HSV图像通道拆分
开发语言·python·opencv
鸿蒙自习室1 小时前
鸿蒙UI开发——组件滤镜效果
开发语言·前端·javascript
言、雲1 小时前
从tryLock()源码来出发,解析Redisson的重试机制和看门狗机制
java·开发语言·数据库