首页 > 其他分享 >动态规划01: 斐波那契数列模型

动态规划01: 斐波那契数列模型

时间:2023-08-05 21:33:01浏览次数:37  
标签:初始化 01 台阶 int 斐波 题目 那契 我们 dp

第 N 个泰波那契数(easy)

题目链接:

1137. 第 N 个泰波那契数

题目描述:

泰波那契序列 Tn 定义如下:

T~0~ = 0, T~1~ = 1, T~2~ = 1, 且在 n >= 0 的条件下 T~n+3~ = T~n~+ + T~n+1~ + T~n+2~

给你整数 n,请返回第 n 个泰波那契数 Tn 的值。

示例 1:

输入:n = 4
输出:4

解释:
T_3 = 0 + 1 + 1 = 2
T_4 = 1 + 1 + 2 = 4

示例 2:

输入:n = 25
输出:1389537

题目解析

注意,我们题目是泰波那契数,不是斐波那契数.

注意一下 T~n~的定义,我们把n直接给赋值为0,此时变化为T~3~ = T~0~+ + T~1~ + T~2~ 也即是 我们 给定一个数据 N, 那么T~N~等于前面三项之和.只要题目中给出我们前三项,那么此时我们就可以推导出第四,五...项.

image-20230803204818326

算法原理

由于我们这里是第一个题目,我们先说一下我们之后要用到的名词.

什么是动态规划,一般而言,我们创建一个一维或者二维数组,我们把他们称之为dp数组,后面我们想办法把这个表给填满,这个数组里面的某一个元素的值就是我们的答案.这个可以作为我们动态规划的流程.

状态表示: 对于数组里面的某一个位置的值可以理解为一个状态表示,他代表了一定含义.我们这里不说状态表示定义是什么,就说怎么得到这个值,毕竟这个名词实在是太严谨,我们先初步理解,有兴趣的朋友可以仔细的去查一下.

转台转移方程,如何得到一位置的确定值就是我们寻找状态转移方程的过程,一般而言,我们得到状态转移方程的思想有下面三个.

  • 题目要求
  • 题目要求+经验
  • 分析问题过程中发现重复子问题

状态表示

这个很简单,我们可以把数组中的元素的值作为泰波那契数列的结果.

dp[i]: 表示 T~i~的值

image-20230803211344206

状态转移方程

这个很简单,题目上已经给了T~i~ = T~i-1~+T~i-2~+T~i-3~

初始化

初始化的目的就是保证不越界,例如本题就是dp[i]要确定值,i必须从3开始出发,毕竟要找到前面三个元素的值,所以初始化为.

dp[0] = 0, dp[1] = 1,dp[2] = 1;

填表顺序

填表的顺序和我们逻辑顺序要一致,例如本题我们dp[i]是要看前面的元素,所谓我们这里从左向右.

返回值

这个看题目要求和我们的状态表示.题目要求我们返回第n个泰波那契数,我们dp[i]表示第n个泰波那契数,所以返回dp[n].

编写代码

class Solution {
public:
    int tribonacci(int n)
    {
        if(n == 0)
            return 0;
        if(n == 1 || n == 2)
            return 1;
        // 我们要返回 dp[n]
        std::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];
    }
};	

image-20230803212927946

空间优化

由于我们是第一题,先把动态规划的完整流程给大家演示一下,后面我们就不谈空间优化的事情了.

对于动规的题目我们一般是使用滚动数组进行优化的,大家看一下这道题目.

image-20230803213332450

我们可以发现我们求dp[i]的时候仅仅需要前面三个元素的值就可以了,像这些dp[i]仅仅需要前面若干的状态的情况我们可以使用滚动数组,例如我们可以定义一个容量为四个元素的数组,依次更新就可以了.

这里说一下优化的结果,一般空间复杂度可以降低一个数量级,例如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;
    int b = 1;
    int c = 1;
    int d = 0;
    for (int i = 3; i <= n; i++)
    {
      d = a + b + c;
      a = b;
      b = c;
      c = d;
    }
    return d;
  }
};

image-20230803214112014

三步问题(easy)

题目链接:

面试题 08.01. 三步问题

题目描述:

三步问题。有个小孩正在上楼梯,楼梯有n阶台阶,小孩一次可以上1阶、2阶或3阶。实现一种方法,计算小孩有多少种上楼梯的方式。结果可能很大,你需要对结果模1000000007。

示例1:

 输入:n = 3 
 输出:4
 说明: 有四种走法

示例2:

 输入:n = 5
 输出:13

题目解析

给定一定数量的台阶,例如10个.其中小孩子它可以一次跳1个台阶,2个台阶,3个台阶.问问我们我们总共有多少种方式可以到达第10个台阶.分析一下示例.

n = 3时, 我们可以这样跳.

方式1 : 1 1 1       每一次跳一个台阶
方式2 : 1 2         第一次跳一个, 第二次跳两个
方式3 : 2 1         第一次跳两个, 第二次跳一个
方式4 : 3           直接跳三个

算法原理

状态表示

这个需要我们的经验了,和数学相关的.我们让dp[i]表示到达第i个台阶我们的方法数.

状态转移方程

dp[i]表示到达第i个台阶的方法数,那么此时我们想如歌可以到达第i给台阶,这里很简单,有三个方式

i-1 下一步跳一个台阶
i-2 下一步跳两个台阶
i-3 下一步跳三个台阶

那么此时dp[i]分别就是这三个方式的和,也就是我们到达i-1和i-2以及i-3的方法数之和,正好dp[i]表示到达第i个台阶的方法数.

i-1 下一步跳一个台阶   dp[i-1] 
i-2 下一步跳两个台阶   dp[i-2]
i-3 下一步跳三个台阶   dp[i-3]

dp[i] = dp[i-1]+ dp[i-2]+ dp[i-3]

初始化

这里存在i-3,所以我们需要进行初始化.

dp[0] = 0, dp[1] = 1,dp[2] = 2; dp[3] = 4;

这里需要说一下,我们也是可以这样初始化的

dp[0] = 1, dp[1] = 1,dp[2] = 2;

这样初始化的意思主要是为了满足dp[3]的求值.

填表顺序

和前面一样从左先右.

返回值

题目要求给定一个n,求到达第n个台阶的方法数,这里就是dp[n]

编写代码

class Solution
{
public:
  int waysToStep(int n)
  {
    if (n == 1)
      return 1;
    if (n == 2)
      return 2;
    std::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] + dp[i - 3];

    return dp[n];
  }
};

image-20230803221139927

这是因为题目中说了数据可能会溢出,所以这里我们两个数相加的时候需要处理一下.

class Solution
{
public:
  int waysToStep(int n)
  {
    if (n == 1)
      return 1;
    if (n == 2)
      return 2;
    const int MOD = 1e9 + 7;
    std::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;

    return dp[n];
  }
};

image-20230803220929446

使用最小花费爬楼梯(easy)

题目链接:

746. 使用最小花费爬楼梯

题目描述:

给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。

你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。

请你计算并返回达到楼梯顶部的最低花费。

示例 1:

输入:cost = [10,15,20]
输出:15
解释:你将从下标为 1 的台阶开始。
- 支付 15 ,向上爬两个台阶,到达楼梯顶部。
总花费为 15 。

示例 2:

输入:cost = [1,100,1,1,1,100,1,1,100,1]
输出:6
解释:你将从下标为 0 的台阶开始。
- 支付 1 ,向上爬两个台阶,到达下标为 2 的台阶。
- 支付 1 ,向上爬两个台阶,到达下标为 4 的台阶。
- 支付 1 ,向上爬两个台阶,到达下标为 6 的台阶。
- 支付 1 ,向上爬一个台阶,到达下标为 7 的台阶。
- 支付 1 ,向上爬两个台阶,到达下标为 9 的台阶。
- 支付 1 ,向上爬一个台阶,到达楼梯顶部。
总花费为 6 。

题目解析

对于给定的数组,我们站在某一个确定的位置,然后花费该位置元素的代价后可以向后面移动一步或者两步,求我们到达楼梯顶部的花费.

此时我们需要知道的是我们的初始位置可以是从下标0或者1开始的.例如示例一数组为 [10,15,20],我们这里从下标1开始,直接跳两步就可以完成了.

在这道题中,数组内的每一个下标 [0, n - 1] 表示的都是楼层,而顶楼的位置其实是在 n 的位置!!!

算法原理

这里我们是题目要求+经验解题.我们用两个方法来解题.

解法一

状态表示

dp[i]表示以i位置为终点花费的最少的花费,注意:到达 i 位置的时候, i 位置的钱不需要算上

状态转移方程

可以到达i位置的有i-1和i-2位置,我们求他们花费的交小值,注意是我们计算较小值的时候是需要加上我们的代价的

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

初始化

这里的初始化有点意思,我们这里有i-2,所以我们这里初始化前两个.我们又可以知道我们初始的时候是可以选择0或者1的,那么0或者1位置的代价是0.所以我们这样做:dp[0] = 0,dp[1] = 0;

填表顺序

从左先右开始填.

返回值

我们要求的是到达n位置的最小花费,此时返回dp[n].

编写代码

class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        int n = cost.size();
        vector<int> dp(n+1, 0);
        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];
    }
};

image-20230805200341962

解法二

状态表示

dp[i]表示以i位置为起点到达终点时需要花费的最少的花费.

状态转移方程

i位置为开始,此时我们可以跳到i+1或者i+2位置,如果i+1位置要跳到终点,按照我们的状态定义,此时应该是dp[i+1],同理i+2是dp[i+2].

我们知道,如果想要跳到i+1位置和i+2位置,我们在i位置是要花费代价的,此时这里的方程是.

dp[i] = min(dp[i+1], dp[i+2]) + const[i];

初始化

这里我们需要依赖i+1或者i+2的值,此时需要初始化dp[n],按照dp[i],我们从n位置到达n位置的花费是0.,这里我们需要注意的我们依赖i+2位置,这个时候我们需要从n-2位置开始填,那么问题来了,dp[n-1]填什么,这个很简单,他跳一步就可以到顶层,那么就是dp[i-1] = const[i-1]

填表顺序

这里是从右向左填.

返回值

按照状态定义,我们是从0位置或者1位置开始出发,到达顶部的最小花费,此时返回dp[0]和dp[1]的较小值.

编写代码

class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        int n = cost.size();
        vector<int> dp(n+1);
        dp[n] = 0;
        dp[n-1] = cost[n-1];
        for(int i = n-2; i >= 0; i--)
        {
            dp[i] = min(dp[i+1], dp[i+2])+ cost[i];
        }
        return min(dp[0], dp[1]);
    }
};

image-20230805200341962

解码方法(medium)

题目链接:

91. 解码方法

题目描述:

一条包含字母 A-Z 的消息通过以下映射进行了 编码

'A' -> "1"
'B' -> "2"
...
'Z' -> "26"

解码 已编码的消息,所有数字必须基于上述映射的方法,反向映射回字母(可能有多种方法)。例如,"11106" 可以映射为:

  • "AAJF" ,将消息分组为 (1 1 10 6)
  • "KJF" ,将消息分组为 (11 10 6)

注意,消息不能分组为 (1 11 06) ,因为 "06" 不能映射为 "F" ,这是由于 "6""06" 在映射中并不等价。

给你一个只含数字的 非空 字符串 s ,请计算并返回 解码 方法的 总数

题目数据保证答案肯定是一个 32 位 的整数。

示例 1:

输入:s = "12"
输出:2
解释:它可以解码为 "AB"(1 2)或者 "L"(12)。

示例 2:

输入:s = "226"
输出:3
解释:它可以解码为 "BZ" (2 26), "VF" (22 6), 或者 "BBF" (2 2 6) 。

示例 3:

输入:s = "06"
输出:0
解释:"06" 无法映射到 "F" ,因为存在前导零("6" 和 "06" 并不等价)。

题目解析

给定一个数字的字符串,我们是可以把它映射成一个只包含字母的字符串的,求我们可以映射多少种.映射的规则是1->a,2->b...26->z.注意一下,我们1可以映射为a,但是01确实不可以的.

算法原理

状态表示

题目要求+经验.

dp[i]: 以i位置为结尾,我们可以映射字符串的个数.

状态转移方程

这里分为两类.

i位置的字符单独作为一个映射, 如果可以映射,那么此时就是dp[i] = dp[i-1]. 注意我们求的是个数,这里是不能加1的.

i位置和前面一个字符进行映射,如果可以话,此时dp[i] = dp[i-2].

这两个情况都是可以的,所以我们的状态方程的结构是dp[i] = dp[i-1]+dp[i-2],具体的分析如下.

image-20230805203637523

初始化

需要用到i-1和i-2,这里我们需要初始化dp[0]和dp[1],上面我们已经用过几次,这里我们换一个初始化方法.我们发现dp[0]比较好初始化,这里只需要关注dp[1],我们申请空间的时候额外申请多申请一个作为辅助节点,看下面.

image-20230805204615052

对于str[1]位置而言,如果单独转化,此时只需要关注前面一个,也就是dp[1].如果和str[0]组合,此时需要知道的时如果可以组合成功,那么dp[2] = dp[0],那么dp[0]应该被初始化1.需要注意的,我们添加辅助节点之后,我们访问str的元素是需要下标适配的.

填表顺序

从左先右.

返回值

返回dp[n],之所以返回n位置的值,是因为我们添加了辅助节点.

编写代码

class Solution {
public:
    int numDecodings(string s) {
        int n = s.size();
        vector<int> dp(n+1, 0);
        dp[0] = 1;
        for(int i = 1; i <= n; i++)
        {
            // 单独一个 i-1 需要匹配
            if(s[i-1] != '0') 
            dp[i] = dp[i-1];

            // 和前面一个字符匹配
            if(i-2>=0 && s[i-2]!='0' && (10*(s[i-2]-'0')+s[i-1]-'0') < 27)
            dp[i] += dp[i-2];
        }
        return dp[n];
    }
};

image-20230805205328365

标签:初始化,01,台阶,int,斐波,题目,那契,我们,dp
From: https://blog.51cto.com/byte/6978268

相关文章

  • P4426 [HNOI/AHOI2018] 毒瘤 题解
    P4426[HNOI/AHOI2018]毒瘤题解非常好虚树题目,融合了容斥的内容。简化题意给定一张\(n\)个点、\(m\)条边的图,求图的独立集个数。其中\(n\leq10^5\),\(n-1\leqm\leqn+10\)。独立集:对于图\(G(U,E)\)的一个点集\(S\),\(\forall(u,v)\inE\),不存在\(u\inS\)且......
  • INS-10013:安装程序检测到此系统的中央资源清册中未注册当前home
    错误信息【汉】INS-10013:安装程序检测到此系统的中央资源清册中未注册当前home【英】INS-10013:Theinstallerhasdetectedthatcurrenthomeisnotregisteredinthecentralinventoryonthissystem.例CentOS7操作系统中,在安装Oracle数据库软件时报错。版本Oracle【11.2.0.......
  • 01手写顺序表
    一、简介学习数据结构的第一个程序,手写实现顺序表。实现功能创建表清空表中元素判断表中数据是否为空求表中有效数据长度指定数据元素定位指定位置插入元素释放空间打印顺序表的内容删除指定位置上的元素二、完整代码sqlist.h#ifndef__SQLIST_H#define__SQLIST......
  • 数据结构-算法-01-算法初步
     ......
  • CH182H与RTL8201F功能对比
    1、CH182H简介CH182H是一款支持Auto-MDIX的工业级10/100M以太网PHY收发器。CH182H内部包括物理编码子层(PCS)、物理介质接入层(PMA)、双绞线物理介质相关子层(TP-PMD)、10BASE-TX编码器/解码器、双绞线介质连接单元(TPMAU)、MII和RMII接口等以太网Transceiver功能所需的模块。CH182H与......
  • 总结: [01背包] 空间优化后内层循环为啥是逆序的?
    总结:[01背包] 空间优化后内层循环为啥是逆序的?首先,这是一个困扰了不少人的问题,虽然网上有挺多的解释,但是有的想起来比较费劲,于是乎,就有了这篇题解题目分析首先,01背包问题是一个非常非常非常经典的动态规划问题(后文简称“动规”或“dp”)。 因为百度百科上的题目分析......
  • [刷题笔记] Luogu P2014 [CTSC1997] 选课
    ProblemSolution我们发现本题中有好多主从关系,即要想取用一个儿子必须先取用她的父亲。构成了一个森林,处理不便。有个小技巧,就是将0号节点参与建树,最后所求节点数就变成了\(m+1\),且把森林变成了一棵树。然后如何处理呢?再次理解题意,我们发现,我们每次的决策是是否取用儿子,取用......
  • [译]2011年移动开发者经济学报告(五)
    哪可以赚钱第二部分将应用推向市场(上)开发者历程移动开发过程复杂,不只是“想法idea”到“应用app”两个步骤。今天的全球应用市场,将想法推向市场需要数十个步骤,仅列几项:计划,开发,调测,论坛支持,测试框架,打包,定价,发布,计费,市场,销售跟踪,用户支持和应用更新。我们用一个非常重要的图来阐述......
  • [译]移动开发在2010年及以后的商用发展走势(七)
    在www.visionmobile.com/rsc/researchreports/MobileDeveloperEconomics2010ReportFINAL.pdf,是一篇很好的文章,想翻译出来,但是逐字翻译,没什么耐性,习惯于散漫自由,且一字一句的翻译,通常有中英文的逻辑差异,也担心翻译得不准确,有不少需要借助Google的翻译,所以还是以笔记的学习方式......
  • 集合(京东2017秋招)
     1#include<stdio.h>2#include<math.h>3intmain(){4inti=0,j,k,h=0,total=0;5intnm[5][2],a[5][10000],b[5][10000],c[20000];6while(scanf("%d%d",&nm[i][0],&nm[i][1])!=EOF){7for(j=0;j<nm[i][0];j++)......