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

目录

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个泰斐波那契数列的值,所以dpi就表示第i个泰斐波那契数列的值

1.1.2 状态转移方程

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

状态转移方程就是dpi等于什么

这道题中dpi = dpi - 1 + dpi - 2 + dpi - 3

1.1.3 初始化

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

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

由题意可知,dp0 = 0,dp1 = dp2 = 1

1.1.4 填表顺序

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

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

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

1.1.5 返回值

题目要求 + 状态表示

这道题就是dpn

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 空间优化

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

优化可以将空间复杂度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 状态表示

dpi表示到达i位置一共有多少种方法

2.1.2 状态转移方程

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

2.1.3 初始化

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

2.1.4 填表顺序

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

2.1.5 返回值

返回dpn

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 状态表示

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

3.1.2 状态转移方程

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

dpi = min(dpi - 1 + costi - 1 , dpi - 2 + costi - 2)

3.1.3 初始化

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

所以,dp0 = dp1 = 0

3.1.4 填表顺序

从左向右

3.1.5 返回值

dpn

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 状态表示

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

3.2.2 状态转移方程

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

dpi = min(dpi - 1 , dpi - 2) + costi

3.2.3 初始化

需要对dpn - 1和dpn - 2初始化

dpn - 1 = costn - 1,dpn - 2 = costn - 2

3.2.4 填表顺序

从右向左

3.2.5 返回值

返回dp0或dp1中的较小值,因为可以选择从下标为 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、解码方法

dpi表示当前位置有几种解码方法

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

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

  1. si可以自身解码,即si != '0'

若si还能和si - 1共同解码,即si和si - 1组成的数字范围在10,26,dpi = dpi - 1 + dpi - 2

若si不能和si - 1共同解码,dpi = dpi - 1

  1. si不能自身解码,即si == '0'

若si还能和si - 1共同解码,即si和si - 1组成的数字范围在10,26,dpi = dpi - 2

若si不能和si - 1共同解码,dpi = 0

通过上面可知,为了防止溢出,我们需要对dp0和dp1初始化

  1. 当s0 == '0',dp0 = dp1 = 0

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

返回值就是dpn - 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];
    }
};
相关推荐
Turbo正则2 小时前
机器学习入门笔记 | 基础算法及其应用场景
笔记·算法·机器学习
东华万里2 小时前
第37篇 手撕二叉树与堆的底层逻辑
数据结构·算法
知无不研2 小时前
算法:判断一个数是不是2的幂
数据结构·算法
从零开始的代码生活_3 小时前
C++ 模板入门:函数模板、类模板与实例化机制
开发语言·c++·后端
luj_176811 小时前
残熵算法实时化三大瓶颈突破
c语言·开发语言·网络·经验分享·算法
不听话坏11 小时前
Ignition篇(下 一) 动态执行前的事情
开发语言·前端·javascript
mCell11 小时前
你以为短链接只是 Hash + 301/302?
后端·算法·架构
likeyi0711 小时前
require 和 import的区别
开发语言·前端
远离UE412 小时前
UE5 compute shader 原子加
开发语言·c++·ue5
C+-C资深大佬12 小时前
C++ 显式类型转换详解:static_cast、dynamic_cast、const_cast、reinterpret_cast
开发语言·c++