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

目录

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];
    }
};
相关推荐
点云侠6 分钟前
PCL 生成三棱锥点云
c++·算法·最小二乘法
代码中介商14 分钟前
跳表:高效查找的链表黑科技
数据结构
兰令水17 分钟前
leecodecode【面试150】【2026.6.13打卡-java版本】
java·算法·leetcode
临沂堇22 分钟前
刷题日志 | Leetcode Hot 100 哈希
算法·leetcode·哈希算法
.道阻且长.27 分钟前
C++ string 操作指南:接口解析
java·c语言·开发语言·c++
蚰蜒螟29 分钟前
Java 对象的内存密语:从字段偏移量计算到 Unsafe 访问的完整链路
java·开发语言
玉小格1 小时前
一次关于Python的总结
算法
星辰_mya1 小时前
CountDownLatch深度解析
java·开发语言·后端·架构
伊甸31 小时前
从企业级项目学敏感词过滤:DFA算法与双层缓存实战
java·算法·缓存
laplaya1 小时前
使用 vcpkg 管理 C++ 项目中的依赖
开发语言·c++