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

news2024/9/25 13:17:37

第 N 个泰波那契数(easy)

题目链接:

1137. 第 N 个泰波那契数

题目描述:

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

T0 = 0, T1 = 1, T2 = 1, 且在 n >= 0 的条件下 Tn+3 = Tn+ + Tn+1 + Tn+2

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

示例 1:

输入:n = 4
输出:4

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

示例 2:

输入:n = 25
输出:1389537

题目解析

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

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

image-20230803204818326

算法原理

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

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

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

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

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

状态表示

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

dp[i]: 表示 Ti的值

image-20230803211344206

状态转移方程

这个很简单,题目上已经给了Ti = Ti-1+Ti-2+Ti-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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/838846.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Vue表格导出Excel数据,自定义表头,使用xlsx-style修饰

继续上篇文章封装导出方法: 效果图&#xff1a; 1、安装xlsx-style依赖&#xff1a; yarn add xlsx-style 2、安装node-polyfill-webpack-plugin依赖&#xff1a; yarn add node-polyfill-webpack-plugin -D 解决报错&#xff1a;jszip is not a constructor 3、配置vue.…

Cilium系列-13-启用XDP加速及Cilium性能调优总结

系列文章 Cilium 系列文章 前言 将 Kubernetes 的 CNI 从其他组件切换为 Cilium, 已经可以有效地提升网络的性能. 但是通过对 Cilium 不同模式的切换/功能的启用, 可以进一步提升 Cilium 的网络性能. 具体调优项包括不限于: 启用本地路由(Native Routing)完全替换 KubeProx…

广西茶叶元宇宙 武隆以茶为媒 推动茶文旅产业融合发展

8月4日&#xff0c;重庆市武隆区启动为期3天的“武隆首届玩茶荟”。本次活动以“中国最美玩茶地——武隆”为主题&#xff0c;吸引众多国内知名专家、茶企和茶馆相关负责人&#xff0c;共同探索武隆茶文旅融合发展新路径和新业态。 广西茶叶元宇宙&#xff1a;广西茶叶元宇宙 …

GD32F103的EXTI中断和EXTI事件

GD32F103的EXTI可以产生中断&#xff0c;也产生事件信号。 GD32F03的EXTI触发源: 1、I/O管脚的16根线&#xff1b; 2、内部模块的4根线(包括LVD、RTC闹钟、USB唤醒、以太网唤醒)。 通过配置GPIO模块的AFIO_EXTISSx寄存器&#xff0c;所有的GPIO管脚都可以被选作EXTI的触发源…

近阶段的一些思考

文章目录 近阶段&#xff08;大约一个多月&#xff09;一直在投入某个开发项目中&#xff0c;没有机会静下来思考一番。对于自己而言&#xff0c;忙碌是一种不好的现象&#xff0c;不应该认为是一件理所当然的事情&#xff0c;应该是一种危机的存在&#xff0c;这种状态持续两周…

关注提示工程—本世纪最重要的技能可能就是与AI人工智能对话

本文目录与主要结构 引言&#xff1a;介绍提示工程的概念和背景&#xff0c;说明为什么它是本世纪最重要的技能之一。 正文&#xff1a; 一、提示工程的基本原理和方法&#xff1a;介绍什么是提示、如何设计和优化提示、如何使用提示与语言模型进行交互。 二、提示工程的应…

Nginx启动报错- Failed to start The nginx HTTP and reverse proxy server

根据日志&#xff0c;仍然出现 “bind() to 0.0.0.0:8888 failed (13: Permission denied)” 错误。这意味着 Nginx 仍然无法绑定到 8888 端口&#xff0c;即使使用 root 权限。 请执行以下操作来进一步排查问题&#xff1a; 确保没有其他进程占用 8888 端口&#xff1a;使用以…

【动态规划】回文子串专题

文章目录 1. 【LeetCode】647. 回文子串1.1 思路讲解1.1.1 方法选择1.1.2 dp表的创建1.1.3 状态转移方程1.1.4 填表顺序 1.2 整体代码 2. 【LeetCode】5. 最长回文串2.1 思路讲解2.2 代码实现 3.【LeetCode】094. 分割回文串II3.1 解题思路3.1.1 创建dp表3.1.2 状态转移方程3.1…

[每日习题]第一个只出现一次的字符 小易的升级之路——牛客习题

hello,大家好&#xff0c;这里是bang___bang_&#xff0c;本篇记录2道牛客习题&#xff0c;第一个只出现一次的字符&#xff08;简单&#xff09;&#xff0c;小易的升级之路&#xff08;简单&#xff09;&#xff0c;如有需要&#xff0c;希望能有所帮助&#xff01; 目录 1️…

继承(Inheritance)

Odoo的一个强大方面是它的模块化。模块专用于业务需求&#xff0c;但模块也可以相互交互。这对于扩展现有模块的功能非常有用。例如&#xff0c;在我们的房地产场景中&#xff0c;我们希望在常规用户视图中直接显示销售人员的财产列表。 在介绍特定的Odoo模块继承之前&#xf…

vue diff 双端比较算法

文章目录 双端指针比较策略命中策略四命中策略二命中策略三命中策略一未命中四种策略&#xff0c;遍历旧节点列表新增情况一新增情况二 删除节点双端比较的优势 双端指针 使用四个变量 oldStartIdx、oldEndIdx、newStartIdx 以及 newEndIdx 分别存储旧 children 和新 children …

26 MFC序列化函数

文章目录 Serialize对于存储文件的序列化 Serialize Serialize 是一个在 MFC (Microsoft Foundation Classes) 中常用的函数或概念。它用于将对象的数据进行序列化和反序列化&#xff0c;便于在不同的场景中保存、传输和恢复对象的状态。 在 MFC 中&#xff0c;Serialize 函数…

如何查询显卡算力

感谢阅读 找到demo_suite此目录下打开控制台 找到demo_suite 一般在 C:\Program Files\NVIDIA GPU Computing Toolkit\CUDA\v11.6\extras\demo_suite 这个目录 注意&#xff1a; 1.V后面的数字表示版本&#xff0c;请根据自己的版本进行更改 2.此目录为我的安装目录&#xff0…

python批量检查folder中的文件是否符合要求

目录 问题描述&#xff1a; 问题解决&#xff1a; 问题描述&#xff1a; 最近在手动整理一些文档&#xff0c;要求是每一个folder以ID命名&#xff0c;每一个folder中存放三个内容&#xff08;如下图&#xff09;。如何实现批量检查每一个folder三个内容是否存在&#xff1f;…

【2023年电赛国一必备】E题报告模板--可直接使用

创作不易&#xff0c;麻烦关注CSDN【技术交流、免费报告资料】 通过百度网盘分享的文件&#xff1a;https://pan.baidu.com/s/1aXzYwLMLx_b59abvplUiYw?pwddn71 提取码:dn71 复制这段内容打开「百度网盘APP 即可获取」 任务 图1 任务内容 要求 图2 基本要求内容 图3 发挥部…

K8s实战入门(三)

文章目录 3. 实战入门3.1 Namespace3.1.1 测试两个不同的名称空间之间的 Pod 是否连通性 3.2 Pod3.3 Label3.4 Deployment3.5 Service 3. 实战入门 本章节将介绍如何在kubernetes集群中部署一个nginx服务&#xff0c;并且能够对其进行访问。 3.1 Namespace Namespace是kuber…

【电源专题】充电IC与DC-DC有什么区别

充电IC和DC-DC一样使用很广泛,如手机、平板等需要电池供电的系统中,一般都会见到充电IC的身影。那么大家有没有考虑过一个问题。充电IC与DC-DC有什么区别? 首先如下所示为充电IC的两个阶段,一个阶段是恒流充电阶段,我们一般称之为CC阶段,另一个是恒压充电阶段,我们称之为…

Java课题笔记~ IoC 控制反转

二、IoC 控制反转 控制反转&#xff08;IoC&#xff0c;Inversion of Control&#xff09;&#xff0c;是一个概念&#xff0c;是一种思想。指将传统上由程序代码直接操控的对象调用权交给容器&#xff0c;通过容器来实现对象的 装配和管理。控制反转就是对对象控制权的转移&a…

ShaderToy着色器移植到Three.js全过程记录

推荐&#xff1a;用 NSDT设计器 快速搭建可编程3D场景。 作为 Publicis Pixelpark Innovationlab 研究的一部分&#xff0c;我们研究了如何将较低底层的语言用于网络技术。 显而易见的选择似乎是 asm.js 或 WebAssembly。 但你也可以使用 WebGL 着色器来解决面向机器的问题。 …

springboot+vue网红酒店客房预定系统的设计与实现_ui9bt

随着计算机技术发展&#xff0c;计算机系统的应用已延伸到社会的各个领域&#xff0c;大量基于网络的广泛应用给生活带来了十分的便利。所以把网红酒店预定管理与现在网络相结合&#xff0c;利用计算机搭建网红酒店预定系统&#xff0c;实现网红酒店预定的信息化。则对于进一步…