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

目录

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];
    }
};
相关推荐
无尽的大道7 分钟前
Java字符串深度解析:String的实现、常量池与性能优化
java·开发语言·性能优化
爱吃生蚝的于勒11 分钟前
深入学习指针(5)!!!!!!!!!!!!!!!
c语言·开发语言·数据结构·学习·计算机网络·算法
羊小猪~~15 分钟前
数据结构C语言描述2(图文结合)--有头单链表,无头单链表(两种方法),链表反转、有序链表构建、排序等操作,考研可看
c语言·数据结构·c++·考研·算法·链表·visual studio
binishuaio20 分钟前
Java 第11天 (git版本控制器基础用法)
java·开发语言·git
zz.YE22 分钟前
【Java SE】StringBuffer
java·开发语言
就是有点傻26 分钟前
WPF中的依赖属性
开发语言·wpf
洋24035 分钟前
C语言常用标准库函数
c语言·开发语言
进击的六角龙36 分钟前
Python中处理Excel的基本概念(如工作簿、工作表等)
开发语言·python·excel
wrx繁星点点37 分钟前
状态模式(State Pattern)详解
java·开发语言·ui·设计模式·状态模式
王哈哈^_^40 分钟前
【数据集】【YOLO】【VOC】目标检测数据集,查找数据集,yolo目标检测算法详细实战训练步骤!
人工智能·深度学习·算法·yolo·目标检测·计算机视觉·pyqt