〖动态规划60题〗泰波纳契数列模型

news2024/12/23 13:40:19

文章目录

  • 1.第N个泰波那契数(简单)
    • 解题流程
      • 1. 状态表示
      • 2. 状态转移方程
      • 3. 初始化dp表
      • 4. 填表顺序
      • 5. 返回值
    • 代码编写
  • 2.三步问题
    • 解题流程
      • 1. 状态表示
      • 2. 状态转移方程
      • 3. 初始化dp表
      • 4. 填表顺序
      • 5. 返回值
    • 代码编写
  • 3.使用最小花费爬楼梯
    • 解题流程
    • 1. 状态表示
      • 2. 状态转移方程
      • 3. 初始化dp表
      • 4. 填表顺序
      • 5. 返回值
    • 代码编写
  • 4.解码方法(中等)
    • 解题流程
      • 1. 状态表示
      • 2. 状态转移方程
      • 3. 初始化dp表
      • 4. 填表顺序
      • 5. 返回值
    • 代码编写

1.第N个泰波那契数(简单)

  • 题目链接:第N个泰波那契数

  • 题目描述

泰波那契序列 Tn 定义如下:
T0 = 0, T1 = 1, T2 = 1, 且在 n >= 0 的条件下 Tn+3 = Tn + Tn+1 + Tn+2
给你整数 n,请返回第 n 个泰波那契数 Tn 的值。

解题流程

1. 状态表示

在解任何一道动态规划题目时,我们都需要先给出一张dp表,用来存储某种状态。

  • dp[i]:在本题目中,dp[i]表示的含义是第i个泰波纳契数的值

2. 状态转移方程

所谓状态转移方程就是,用已经存在的状态来推出将要发生的状态。

在本题中,状态转移方程为——

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

3. 初始化dp表

从状态转移方程中,我们发现当 i < 3 时,方程是失效的,因为会出现 dp 表的下标为负数的情况。所以,也就是说,当 i = 0,i = 1,i = 2时,不能依靠状态转移方程推出 dp[i] 的值,则需要我们手动进行初始化,即:

  • dp[0] = 0dp[1] = 1dp[2] = 1

4. 填表顺序

填表顺序即,填 dp 表的顺序。

  • 本题中,我们发现要想推导出当前位置的值,首先要知道前面 3 个 dp 值,所以,填表顺序为从左往右

5. 返回值

  • dp[i] 的含义本来就是第 i 个泰波纳契数的值,所以返回值为dp[n]

代码编写

C++

class Solution {
public:
    int tribonacci(int n) {
        // 特殊情况处理
        if(n == 0) return 0;
        if(n == 1 || n == 2) return 1;
        
        // 创建dp表
        vector<int> dp(n+1);

        // 初始化
        dp[0] = 0; dp[1] = 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];
    }
};

JAVA

class Solution
{
	public int tribonacci(int n)
	{
		// 1. 创建 dp 表
		// 2. 初始化
		// 3. 填表
		// 4. 返回结果
		// 处理边界情况
		if(n == 0) return 0;
		if(n == 1 || n == 2) return 1;
		int[] dp = new int[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];
	}
}

2.三步问题

  • 题目链接:三步问题

  • 题目描述

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

解题流程

1. 状态表示

  • dp[i]:到达第 i 个台阶有多少种方法

2. 状态转移方程

题目中这个小孩的一次最多能跨三个台阶,也就是说当我们要计算到达 i 位置时有多少种方式,可以转化为3种情况:

  • ① 先到达 i-1 位置,然后跨一阶到达 i;
  • ② 先到达 i-2 位置,然后一次性跨两阶到达 i;
  • ③ 先到达 i-3 位置,然后一次性跨三阶到达 i

也就是说,我们可以先到达 i-1 或 i-2 或 i-3 位置,再走一步就可以到达i位置。所以到达 i 位置有多少种方法其实就是——到达前 i 的三个位置的方法数的和。

所以可以得出状态转移方程为:

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

3. 初始化dp表

从状态转移方程中,我们发现当 i < 3 时,方程是失效的,因为会出现 dp 表的下标为负数的情况。所以,也就是说,当 i = 0,i = 1,i = 2 时,不能依靠状态转移方程推出 dp[i] 的值,则需要我们手动进行初始化,即:

  • dp[1] = 1dp[2] = 2dp[3] = 4(i=0时无意义)

4. 填表顺序

  • 本题中,我们发现要想推导出当前位置的值,首先要知道前面 3 个 dp 值,所以,填表顺序为从左往右

5. 返回值

  • dp[i] 的含义为到达第 i 个台阶有多少种方法,所以返回值为dp[n]

代码编写

C++

class Solution {

const int MOD = 1e9 + 7; // 用来取模

public:
    int waysToStep(int n) {
        if(n == 1) return 1;
        if(n == 2) return 2;
        if(n == 3) return 4;

        // 创建dp表
        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];
    }
};

JAVA

class Solution
{
	public int waysToStep(int n)
	{
		// 1. 创建 dp 表
		// 2. 初始化
		// 3. 填表
		// 4. 返回值
		int MOD = (int)1e9 + 7;
		// 处理⼀下边界情况
		if(n == 1 || n == 2) return n;
		if(n == 3) return 4;
		int[] dp = new int[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];
	}
}

3.使用最小花费爬楼梯

  • 题目链接:使用最小花费爬楼梯

  • 题目描述

给你一个整数数组 cost ,其中 cost[i] 是从楼梯第 i 个台阶向上爬需要支付的费用。一旦你支付此费用,即可选择向上爬一个或者两个台阶。
你可以选择从下标为 0 或下标为 1 的台阶开始爬楼梯。
请你计算并返回达到楼梯顶部的最低花费。

解题流程

1. 状态表示

  • dp[i]:到达第 i 个台阶的最小花费

2. 状态转移方程

由题可知,到达第 i 个台阶,有两种方式:

  • ① 从 i-1 的位置到达 i,花费为 dp[i-1] + cost[i-1]
  • ② 从 i-2 的位置到达 i,花费为 dp[i-2] + cost[i-2]

解释:要想计算 i-1 到达 i 的最小花费,首先要保证到达 i-1 时,所用的花费为最小花费,最小花费为 dp[i-1] 。其次,要想从 i-1 位置到达 i,还得加上 i-1 台阶需要交付的费用,即 cost[i-1]。i-2 同理。

所以,到达i有两种方式,我们选择两种方式中,花费最小的那个,所以需要比较,即 min(方式1,方式2)。所以我们可以得出状态转移方程为:

  • dp[i] = min(dp[i-1] + cost[i-1], dp[i-2] + cost[i-2])

3. 初始化dp表

题目上说,你可以从0或1位置开始爬,所以意味着到达0和1位置是免费的,即:

  • dp[0] = 0; dp[1] = 0

4. 填表顺序

  • 本题中,我们发现要想推导出当前位置的值,首先要知道前面2个dp值,所以,填表顺序为从左往右

5. 返回值

  • 有状态表示可知,应返回dp[n]的值;

代码编写

C++

class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        int n = cost.size();

        // 创建一个dp表
        vector<int> dp(n+1);

        // 初始化
        dp[0] = 0; dp[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];
    }
};

JAVA

class Solution
{
	public int minCostClimbingStairs(int[] cost)
	{
		// 1. 创建 dp 表
		// 2. 初始化
		// 3. 填表
		// 4. 返回值
		int n = cost.length;
		int[] dp = new int[n + 1];
		for(int i = 2; i <= n; i++)
			dp[i] = Math.min(dp[i - 1] + cost[i - 1], dp[i - 2] + cost[i - 2]);
		return dp[n];
	}
}

4.解码方法(中等)

  • 题目链接:解码方法

  • 题目描述

一条包含字母 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 ,请计算并返回 解码 方法的 总数 。

解题流程

1. 状态表示

  • dp[i]:在字符串区间[0,i]上共有dp[i]种解码方法

2. 状态转移方程

对于i位置有多少种解码方法,可从两方面来看:

  • ① i 位置可以单独解码成功;
  • ② i 位置与 i-1 位置组合成一个字母解码成功;

对上面两种情况单独进行分析,每种情况又可以分为两种情况:

对于单独解码的情况

  • Ⅰ. 解码成功:如果 s[i] 不等于零,即可解码成功 (因为 0 没有对应的编码)。此时 dp[i] 就等于 dp[i-1] ,因为 i 可以单独解码,不会影响总的方法,例如 [9] [9,6] ,加入了 6 之后并不会影响编码结果;
  • Ⅱ. 解码失败:如果 s[i] 等于 0 ,解码失败,dp[i] = 0;因为一个字符串中,当你以某种方法进行解码,留下了最后一个数字是个 0,解码失败了,证明你的方法是错的。例如下面这个字符串就会解码失败。
    在这里插入图片描述

对于组合解码的情况

  • Ⅰ. 解码成功:如果 i 位置与 i-1 位置组合起来是 [10,26] 的数字,即解码成功,此时 dp[i] = dp[i-2],原因同上;
  • Ⅱ. 解码失败:如果两位组合是 ”06“、”09“ 这样的数字,即为解码失败,若大于 26,也为失败,此时 dp[i]=0。

综上所述,我们可以得到状态转移方程为:

  • 当 s[i] 上的数在[1, 9] 区间上时: dp[i] += dp[i - 1]
  • 当 s[i - 1] 与 s[i] 上的数结合后,在 [10, 26] 之间的时候: dp[i] +=dp[i - 2] ;

3. 初始化dp表

方法一 直接初始化

与之前的情况相同,由于需要用到 dp[i-1],dp[i-2] 的值,所以i需大于等于 2,dp[0],dp[1] 的值需要手动赋值。

  • 如果 s[0] 等单独解码成功,则 dp[0] = 1,否则dp[0] = 0
  • 如果 s[1] 能单独解码成功,则 dp[1] += dp[0];
  • 如果 s[1] 与 s[0] 组合解码成功,则 dp[1] += 1;

方法二 辅助位初始化
可以在最前⾯加上⼀个辅助结点,帮助我们初始化。使⽤这种技巧要注意两个点:

  • 辅助结点里面的值要保证后续填表是正确的
  • 下标的映射关系

4. 填表顺序

  • 本题中,我们发现要想推导出当前位置的值,首先要知道前面2个 dp 值,所以,填表顺序为从左往右

5. 返回值

  • 方法一:应该返回dp[n - 1]的值,表⽰在 [0, n - 1] 区间上的编码方法。
  • 方法二:由于多增加了一个辅助位,所以返回值要后移一位

代码编写

C++ 方法一

class Solution {
public:
    int numDecodings(string s) {
        int n = s.size();

        // 创建一个dp表
        vector<int> dp(n);

        // 初始化
        dp[0] = s[0] != '0';

        // 处理边界情况
        if (n == 1) return dp[0];

        if (s[1] <= '9' && s[1] >= '1') dp[1] += dp[0];

        int t = (s[0] - '0') * 10 + (s[1] - '0');
        if (t <= 26 && t >= 10) dp[1] += 1;

        // 填表
        for (int i = 2; i < n; i++)
        {
            if (s[i] == '0') dp[i] = 0;

            if (s[i] <= '9' && s[i] >= '1') dp[i] += dp[i - 1];

            int t = (s[i - 1] - '0') * 10 + (s[i] - '0');
            if (t <= 26 && t >= 10) dp[i] += dp[i - 2];
        }

        // 返回
        return dp[n - 1];
    }
};

C++ 方法二

class Solution {
public:
    int numDecodings(string s) {
        int n = s.size();

        // 创建一个dp表
        vector<int> dp(n+1);

        // 初始化
        dp[0] = 1; // 辅助位
        dp[1] = s[0] != '0';

        // 填表
        for(int i = 2; i <= n; i++)
        {
            if(s[i] <= '9' && s[i] >= '1') dp[i] += dp[i-1];

            int t = (s[i-1] - '0')* 10 + (s[i] - '0');
            if(t <= 26 && t >= 10) dp[i] += dp[i-2];
        }

        // 返回
        return dp[n-1];
    }
};

JAVA

class Solution
{
	public int numDecodings(String ss)
	{
		// 1. 创建 dp 表
		// 2. 初始化
		// 3. 填表
		// 4. 返回值
		int n = ss.length();
		char[] s = ss.toCharArray();
		int[] dp = new int[n + 1];
		dp[0] = 1; // 保证后续填表是正确的
		if(s[1 - 1] != '0') dp[1] = 1;
		for(int i = 2; i <= n; i++)
		{
			// 先处理第⼀种情况
			if(s[i - 1] != '0') dp[i] += dp[i - 1];
			// 处理第⼆种情况
			int tt = (s[i - 2] - '0') * 10 + s[i - 1] - '0';
			if(tt >= 10 && tt <= 26) dp[i] += dp[i - 2];
		}
		return dp[n];
	}
}

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

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

相关文章

Redis高可用——主从复制

redis的主从复制 一、Redis 主从复制1.主从复制的作用&#xff1a;2.主从复制流程&#xff1a; 二、搭建Redis 主从复制1.安装 Redis①.环境准备②.修改内核参数③.安装redis④.创建redis工作目录⑤.环境变量⑥.定义systemd服务管理脚本 2.修改 Redis 配置文件&#xff08;Mast…

阅读源码技巧

目录 搭建 Demo方法论之关注调用栈方法论之死盯日志方法论之查看被调用的地方探索答案作业是的,正如标题描述的这样,我试图通过这篇文章,教会你如何阅读源码。 事情大概是这样的,前段时间,我收到了一个读者发来的类似于这样的示例代码: 他说他知道这三个案例的回滚情况是…

详解TCP

目录 1.TCP概论 1.1.什么是TCP 1.2.TCP连接的建立过程 1.3.TCP的传输过程 1.4.TCP连接的释放过程 2.JAVA中的TCP 3.TCP带来的一些性能问题 1.TCP概论 1.1.什么是TCP 为了保证所有设备能相互通信&#xff0c;从而建立了一张互联互通的计算机网络&#xff0c;在这张大网…

使用java.lang.Record类删除样板代码

样板是一个源自钢铁制造业的术语&#xff0c;其中形成模具以铸造类似的物体。在编程世界中&#xff0c;样板代码是代码的一部分&#xff0c;项目里面使用的地方很多&#xff0c;但是通常创建完成之后就很少或者就不会更改了。在Java中&#xff0c;不可变的数据载体类用于与数据…

SpringBoot第13讲:SpringBoot接口如何参数校验国际化

SpringBoot第13讲&#xff1a;SpringBoot接口 - 如何参数校验国际化 本文是SpringBoot第13讲&#xff0c;上文我们学习了如何对SpringBoot接口进行参数校验&#xff0c;但是如果需要有国际化的信息&#xff08;比如返回校验结果有中英文&#xff09;&#xff0c;应该如何优雅处…

【C++】vector介绍及使用

&#x1f680; 作者简介&#xff1a;一名在后端领域学习&#xff0c;并渴望能够学有所成的追梦人。 &#x1f681; 个人主页&#xff1a;不 良 &#x1f525; 系列专栏&#xff1a;&#x1f6f8;C &#x1f6f9;Linux &#x1f4d5; 学习格言&#xff1a;博观而约取&#xff0…

【智能座舱系列| 域控制器】——座舱域控制器

传统多芯片架构 原来的座舱里面的控制器基本上是分开的,导航主机是一家,液晶仪表是一家,同时还有一个AVM全景一家,还有TBOX等,这里线束连接就非常复杂,而且不同供应商直接的协调调试也非常复杂。 上图是IMX6 的多芯片方案,液晶仪表、中控导航、后排娱乐都使用了IMX6最小…

axure可视化大屏模板200例 •axure模板 大屏可视化 •axure数据可视化原型 •axure可视化组件 •axure

可视化axure原型可视化大屏模板200例&#xff0c;带动画效果&#xff0c;可直接复用 axure可视化大屏模板200例 axure可视化大屏模板200例数据可视化原型可视化组件下载—无极低码 axure模板 大屏可视化axure数据可视化原型axure可视化组件axure原型演示axure绘制界面原型图…

AI日报|哈佛“AI教授”即将上线;首个生成式AI技能专业证书来了;电话推销员很烦?AI帮你“制裁”他

今日值得关注的人工智能新动态&#xff1a; 将GPT-4用在课程设计中 哈佛大学“AI教授”即将上线 微软推出首个生成式AI技能专业证书 纽约州议会&#xff1a;伤害或羞辱他人的deepfake是非法的 阿诺德施瓦辛格&#xff1a;《终结者》中的AI已成现实 AI诊断“老年痴呆”&…

大数据分析案例-基于决策树算法构建信用卡欺诈识别模型

&#x1f935;‍♂️ 个人主页&#xff1a;艾派森的个人主页 ✍&#x1f3fb;作者简介&#xff1a;Python学习者 &#x1f40b; 希望大家多多支持&#xff0c;我们一起进步&#xff01;&#x1f604; 如果文章对你有帮助的话&#xff0c; 欢迎评论 &#x1f4ac;点赞&#x1f4…

数字IC验证的学习路径,新人必备!

优秀的验证工程师&#xff0c;需要掌握的技能有很多&#xff0c;兼备硬件和软件&#xff0c;可以说是更加触类旁通。所以还是需要循序渐进&#xff0c;从最基础的内容开始逐步掌握。 文内所有的学习资料&#xff0c;面试题目&#xff08;文末可全领&#xff09; 1.数电和veri…

VS2015 修复失败

Repair/Modify operation did not finish successfully. “Setup error – repair/modify operation did not finish successfully.” message shown when starting Visual Studio 2015 Problem: When starting Visual Studio 2015, the following message is shown “Setu…

企业实施CIS控制

什么是CIS控件 CIS 关键安全控制由互联网安全中心开发&#xff0c;是一套规范性的、优先的网络安全最佳实践和防御措施&#xff0c;可以帮助防止最普遍和最危险的攻击&#xff0c;并支持多框架时代的合规性。 这些可操作的网络防御最佳实践由一组 IT 专家使用从实际攻击及其有…

Elasticsearch:映射(mapping)的实用实践指南

动态映射适用于开发环境&#xff0c;但对于生产级集群禁用它。 将动态配置为 “strict” 以对索引的字段值实施严格模式。有关动态映射的详细描述&#xff0c;请阅读文章 “Elasticsearch&#xff1a;Dynamic mapping”。 PUT /twitter {"mappings": {"dynamic…

Python——对文件的操作

一、 文件的读写 读文件&#xff1a;从磁盘打开 写文件&#xff1a;将文件存入磁盘 使用内置函数open()创建文件对象。 格式为&#xff1a; file open(filename [,mode,encoding])其中 file ——被创建的文件对象 open ——创建文件对象的函数 filename ——要创建或要打开…

全新特征融合模块AFPN,完胜PAFPN

直接看图说话 论文地址&#xff1a;https://arxiv.org/abs/2306.15988v1 代码地址&#xff1a; GitHub - gyyang23/AFPN 多尺度特征在目标检测任务中对具有尺度方差的目标进行编码时具有重要意义。多尺度特征提取的一种常见策略是采用经典的自上而下和自下而上的特征金字塔网络…

【线程】线程概念及相关函数实现

目录 0. 线程的概念 1. 线程的基本操作 1.1 线程的创建&#xff1a;pthread_create函数 1.2 线程等待&#xff1a;pthread_join函数 1.3 线程的分离&#xff1a;pthread_detach函数 1.4 线程退出&#xff1a;pthread_exit函数 1.5 线程的取消&#xff1a;pthread_cancel…

了解Spring

目录 什么是Spring? DI Spring 存与取 spring 的存操作 spring的取操作 更快速的进行 Spring 存 与 读 三大注入方式 Autowired set 注入 构造方法注入 Spring 容器中有多个相同的类时 Bean 作用域 设置作用域 Spring 执行流程 Bean 生命周期 什么是Spring? Sp…

【密码学基础】半/全同态加密算法基础学习笔记

文章目录 1 半同态加密Pailliar加法同态加密Paillier加解密过程Paillier的同态性Paillier的安全性 El Gamal乘法同态加密RSA乘法同态加密 2 全同态加密BFV全同态加密BFV的编码方式BFV加解密过程BFV的安全性BFV的同态性自举Bootstrapping 3 同态加密应用场景场景1&#xff1a;安…