动态规划课堂1-----斐波那契数列模型

目录

动态规划的概念:

动态规划的解法流程:

题目: 第 N 个泰波那契数

解法(动态规划)

代码:

优化:

题目:最小花费爬楼梯

解法(动态规划)

解法1:

解法2:

题目:解码方法

解法(动态规划)

结语:


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

动态规划的概念:

动态规划(英语:Dynamic programming,简称 DP),是一种在数学、管理科学、计算机科学、经济学和生物信息学中使用的,通过把原问题分解为相对简单的子问题的方式求解复杂问题的方法。动态规划常常适用于有重叠子问题和最优子结构性质的问题。

动态规划的解法流程:

1.状态表示

dp问题的基础,自己要确定dp表每一个下标值的含义,这是用动态规划解决问题的第一步,只有把这一步确定了再去推出下面的状态转移方程,第一第二步完成后那么dp问题就已经解决了99%因为剩下的345就是处理边界和一些细节问题。

2.状态转移方程

推出状态转移方程可以说是dp问题最难的一步,如果在选定的状态表示下推不出状态转移方程,那么可能要换一个状态表示,因为状态表示可能是错误的。

3.初始化

一般初始化dp[0]和dp[1] .

4.填表顺序

一般有从左向右和从右先左,这取决于题目(覆盖问题)。

5.返回值

最后的返回值(不一定是dp[n]).

由于是算法只讲知识点是远远不够的,故下面我会用例题来帮助大家理解(例题的链接会在最后给出)。

到这一些基本概念就讲解完毕下面开始用题目要带友友更加深入学习。

题目: 第 N 个泰波那契数

题目链接1137. 第 N 个泰波那契数

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

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

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

解法(动态规划)

1. 状态表示:

根据题目来推出状态表示,后面的大部分题目是要经验+题目来推出的

这道题可以「根据题目的要求」直接定义出状态表示:

dp[i] 表示:第i 个泰波那契数的值。

2.状态转移方程

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

3.初始化

从我们的递推公式可以看出, dp[i] 在 i = 0 以及i = 1 的时候是没有办法进行推导的,因 为dp[-2] 或dp[-1] 不是⼀个有效的数据。因此我们需要在填表之前,将0, 1, 2 位置的值初始化。题目中已经告诉我们dp[0] = 0, dp[1] = dp[2] = 1 。(处理一些边界问题)

4.填表顺序

毫无疑问是「从左往右」。

5.返回值:

应该返回dp[n] 的值。

代码:

dp问题的代码编写流程一般比较固定分为1.创建dp表,2.初始化,3.填表,4.返回值.

最上面两个if用来解决边界问题。

class Solution {public int tribonacci(int n) {//1.创建dp表//2.初始化//3.填表//4.返回值int[] dp = new int[n + 1];if(n == 0){return 0;}if(n == 1 || n == 2){return 1;}dp[0] = 0;dp[2] = dp[1] = 1;for(int i = 3;i <= n;i++){dp[i] = dp[i - 3] + dp[i - 2] + dp[i - 1];}return dp[n];}
}

 

优化:

下面动图来自力扣。

一般是利用滚动数组优化(可以是一个小数组也可以是几个变量)

代码如下:

其实就是把表变成几个变量把空间复杂度降低到O(1)。

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 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;}
}

类似题:和上面那题类似给大家练手。

三步问题

参考代码如下:

其中dp[i] 表示:到达i位置时,⼀共有多少种方法。

通过分析知道第i步的方法为前三步方法的总和故状态转移方程为dp[i] = dp[i - 1] + dp[i - 2] + dp[i - 3]。

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 - 3] + dp[i - 2]) % MOD+ dp[i - 1]) % MOD;} return dp[n];}
}

题目:最小花费爬楼梯

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

注意这里的顶部不是数组的最后一个位置,而是在数组最后一个位置再后面一个。 

解法(动态规划)

解法1:

1. 状态表示:

这道题可以根据「经验+题⽬要求」直接定义出状态表示:dp[i] 表示:到达i 位置时的最小花费。(注意:到达i 位置的时候, i 位置的钱不需要算上)。

2.状态转移方程

根据最近的⼀步,分情况讨论:

(1)先到达i - 1 的位置,然后⽀付cost[i - 1] ,接下来⾛⼀步⾛到i 位置: dp[i - 1] + csot[i - 1] 。

(2)先到达i - 2 的位置,然后⽀付cost[i - 2] ,接下来⾛⼀步⾛到i 位置: dp[i - 2] + csot[i - 2] 。

3.初始化

从我们的递推公式可以看出,我们需要先初始化i = 0 ,以及i = 1 位置的值。容易得到dp[0] = dp[1] = 0 ,因为不需要任何花费,就可以直接站在第0 层和第1 层上。

4.填表顺序

根据「状态转移方程」可得,遍历的顺序是「从左往右」。

5.返回值

根据「状态表⽰以及题目要求」,需要返回dp[n] 位置的值。

代码:

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

解法2:

解法一和解法二的区别就是状态表示不一样,这样再描述一个解法二是为了告诉大家解法不一定只有一种选定状态表示去试一下状态转移方程(不要怕错)。

1. 状态表示:

dp[i] 表示:从i 位置出发,到达楼顶,此时的最小花费。

2.状态转移方程:

根据最近的⼀步,分情况讨论:

(1)支付cost[i] ,往后走⼀步,接下来从i + 1 的位置出发到终点: dp[i + 1] + cost[i] ;

(2)支付cost[i] ,往后走⼀步,接下来从i + 1 的位置出发到终点: dp[i + 1] + cost[i] ;

我们要的是最小花费,因此dp[i] = min(dp[i + 1], dp[i + 2]) + cost[i] 。

剩下三步我就不多赘述。

代码如下:

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

题目:解码方法

解法(动态规划)

1. 状态表示:

根据以往的经验,对于⼤多数线性dp ,我们经验上都是「以某个位置结束或者开始」做文章,这 ⾥我们继续尝试「⽤i位置为结尾」结合「题⽬要求」来定义状态表⽰。dp[i] 表⽰:字符串中[0,i] 区间上,⼀共有多少种编码⽅法。

2.状态转移方程

关于i 位置的编码状况,我们可以分为下⾯两种情况:

(1)让i 位置上的数单独解码成⼀个字⺟。

(2)让i 位置上的数与i - 1 位置上的数结合,解码成⼀个字⺟。

让i位置上的数单独解码成⼀个字⺟,就存在「解码成功」和「解码失败」两种情况:

(1)当i位置上的数单独解码成⼀个字⺟。

解码成功:当i 位置上的数在[1, 9] 之间的时候,说明i 位置上的数是可以单独解 码的,那么此时[0, i] 区间上的解码⽅法应该等于[0, i - 1] 区间上的解码方法。因为[0, i - 1] 区间上的所有解码结果,后⾯填上⼀个i 位置解码后的字⺟就 可以了。此时dp[i] = dp[i - 1] ;

解码失败:当i 位置上的数是0 的时候,说明i 位置上的数是不能单独解码的,那么 此时[0, i] 区间上不存在解码⽅法。因为i 位置如果单独参与解码,但是解码失败了,那么前⾯做的努⼒就全部⽩费了。此时dp[i] = 0 。

(2)让i 位置上的数与i - 1 位置上的数结合,解码成⼀个字⺟。

解码成功:当结合的数在[10, 26] 之间的时候,说明[i - 1, i] 两个位置是可以 解码成功的,那么此时[0, i] 区间上的解码⽅法应该等于[0, i - 2 ]区间上的解码 ⽅法,原因同上。此时dp[i] = dp[i - 2] ;

解码失败:当结合的数在[0, 9] 和[27 , 99] 之间的时候,说明两个位置结合后解 码失败(这⾥⼀定要注意00 01 02 03 04 ......这⼏种情况),那么此时[0, i] 区间上的解码⽅法就不存在了,原因依旧同上。此时dp[i] = 0 。

综上所述: dp[i] 最终的结果应该是上⾯四种情况下,解码成功的两种的累加和(因为我们关⼼的是解码⽅法,既然解码失败,就不⽤加⼊到最终结果中去),因此可以得到状态转移⽅程( dp[i] 默认初始化为0 ):

(1)当s[i] 上的数在[1, 9] 区间上时: dp[i] += dp[i - 1] ;

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

如果上述两个判断都不成⽴,说明没有解码⽅法, dp[i] 就是默认值0 。

3.初始化

(1)当s[0] != '0' 时,能编码成功, dp[0] = 1 初始化dp[1] :

(2)当s[1] 在[1,9] 之间时,能单独编码,此时dp[1] += dp[0] (原因同上, dp[1] 默认为0 )

(3)当s[0] 与s[1] 结合后的数在[10, 26] 之间时,说明在前两个字符中,⼜有⼀种 编码⽅式,此时dp[1] += 1 。

4.填表顺序

毫⽆疑问是「从左往右」

5.返回值

应该返回dp[n - 1] 的值,表⽰在[0, n - 1] 区间上的编码⽅法。

代码:

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];if(s[0] != '0') dp[0] = 1; // 初始化第⼀个位置 if(n == 1) return dp[0]; // 处理边界情况 // 初始化第⼆个位置 if(s[1] != '0' && s[0] != '0') dp[1] += 1;int t = (s[0] - '0') * 10 + s[1] - '0';if(t >= 10 && t <= 26) dp[1] += 1;for(int i = 2; i < n; i++){// 先处理第⼀种情况 if(s[i] != '0') dp[i] += dp[i - 1];// 处理第⼆种情况 int tt = (s[i - 1] - '0') * 10 + s[i] - '0';if(tt >= 10 && tt <= 26) dp[i] += dp[i - 2];}return dp[n - 1];}
}

优化:

添加辅助位置初始化

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

(1)辅助结点⾥⾯的值要保证后续填表是正确的; 

(2)下标的映射关系

使用这种方式可以减少初始化的负担dp[1]就可以不用初始化,且不用考虑边界问题,因为我们的dp数组会开辟n+1,里面原本的数据都向后移动一位,dp[0]一般是0或者1(具体看题)。

代码如下:

class Solution {public int numDecodings(String s) {//1创建dp表//2初始化//3填表//4返回值char[] ss = s.toCharArray();int n = s.length();int[] dp = new int[n + 1];dp[0] = 1;if(ss[0] != '0'){dp[1] = 1;}for(int i = 2;i <= n;i++){if(ss[i - 1] != '0'){dp[i] += dp[i - 1];}int tt = (ss[i - 2] - '0') * 10 + ss[i - 1] - '0';if(tt >= 10 && tt <= 26){dp[i] += dp[i - 2];}}return dp[n];}
}

结语:

其实写博客不仅仅是为了教大家,同时这也有利于我巩固自己的知识点,和一个学习的总结,由于作者水平有限,对文章有任何问题的还请指出,接受大家的批评,让我改进,如果大家有所收获的话还请不要吝啬你们的点赞收藏和关注,这可以激励我写出更加优秀的文章。

 

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

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

相关文章

独立站建站全攻略:从0到1打造专属在线商业平台

独立站建站全攻略&#xff1a;从0到1打造专属在线商业平台 随着互联网的普及和发展&#xff0c;越来越多的企业和个人开始认识到拥有一个独立站的重要性。独立站不仅可以提升品牌形象&#xff0c;还能为企业带来更多的流量和潜在客户。本文将为大家详细介绍独立站建站的全过程…

如何实现不同 Vue 项目的 npm 和 Node.js 环境进行隔离

方法一&#xff1a;使用 nvm&#xff08;Node Version Manager&#xff09; nvm 是一个用于管理多个 Node.js 版本的工具。通过 nvm&#xff0c;你可以为每个 Vue 项目安装和使用不同版本的 Node.js 和 npm。首先&#xff0c;安装 nvm&#xff1a;对于 macOS 和 Linux&#xf…

【深度学习笔记】卷积神经网络——汇聚层(池化层)

汇聚层&#xff08;池化层&#xff09; 通常当我们处理图像时&#xff0c;我们希望逐渐降低隐藏表示的空间分辨率、聚集信息&#xff0c;这样随着我们在神经网络中层叠的上升&#xff0c;每个神经元对其敏感的感受野&#xff08;输入&#xff09;就越大。 而我们的机器学习任…

VsCode的leetcode插件无法登录

前提 想使用VsCode的leetcode插件进行刷题&#xff0c;然后按照网上的教程进行安装下载&#xff0c;但是到了登录这一步&#xff0c;死活也登录不了&#xff0c;然后查看log一直报的错误是invalid password。 解决方法 首先确定在插件中设置的站点是Leetcode中国&#xff0c…

图像处理新框架 | 语义与复原指令双引擎,谷歌研究院提出文本驱动图像处理框架TIP

本文首发: AIWalker 欢迎关注AIWalker&#xff0c;底层视觉与基础AI技术 https://arxiv.org/abs/2312.14091 https://github.com/Picsart-AI-Research/HD-Painter 基于文本到图像扩散模型的空前成功&#xff0c;文本引导图像修复的最新进展已经可以生成非常逼真和视觉上合理的结…

C++面试:linux系统性能监控命令的使用

目录 1. top 2. vmstat 3. iostat 4. mpstat 5. netstat 6. sar 7. htop 8. dstat 9. free 10. lsof 11. pidstat 12. nmon 13. iftop 14. glances 面试准备小贴士 在Linux系统管理和故障排查中&#xff0c;使用性能监控工具是非常重要的。这些工具可以帮助你理…

centos7部署单机项目和自启动

centos7部署单机项目和服务器自启动 1.安装jdk和tomact1.1上传jdk、tomcat安装包1.2解压两个工具包1.3.配置并且测试jdk安装1.4.启动tomcat1.5.防火墙设置1.6配置tomcat自启动 2.安装mysql2.1卸载mariadb&#xff0c;否则安装MySql会出现冲突(先查看后删除再查看)2.2在线下载My…

【爬虫逆向实战篇】定位加密参数、断点调试与JS代码分析

文章目录 1. 写在前面2. 确认加密参数3. 加密参数定位4. XHR断点调试 【作者主页】&#xff1a;吴秋霖 【作者介绍】&#xff1a;Python领域优质创作者、阿里云博客专家、华为云享专家。长期致力于Python与爬虫领域研究与开发工作&#xff01; 【作者推荐】&#xff1a;对JS逆向…

python-分享篇-用python制作九宫格切图器

文章目录 代码效果 代码 import tkinter as tk from PIL import Image,ImageTk import sys import tkinter.filedialog#先将图片填充为正方形 def fill_image(image): width, height image.size #比较图片的宽和高&#xff0c;选取值较大的作为新图的宽 newImage_width wid…

仿12306校招项目-项目业务和架构

目录 业务图 用户管理 业务难点 1. 如何确定用户注册信息的真实性 2. 面对亿级用户量 3. 支持多种登录方式会造成读请求扩散&#xff0c;需要解决用户定位问题 4. 高并发场景下缓存穿透问题需要有效解决&#xff0c;避免数据库压力过大 5. 明文存储用户敏感信息会造成安…

抽象的java

Consider defining a bean of type org.springframework.mail.MailSender in your configuration. 报错原因&#xff1a; 第一个&#xff1a;未安装对应的依赖 第二个&#xff1a;对应配置问题 背景&#xff1a;用springboot-java完成邮箱发送 第一个问题解决方法&#xff1…

实战一个 Jenkins 构建 CI/CD流水线 的简单配置过程哈

引言&#xff1a;上一期我们讲述了gitlabCI/CD工具的介绍&#xff0c;工具之争&#xff0c;本期我们介绍Jenkins CI/CD 目录 一、Jenkins介绍 1、Jenkins概念 2、Jenkins目的 3、特性 4、产品发布流程 二、安装Jenkins 1、安装JDK 2、安装Jenkins 1、上传压缩包 2、…

Spark之【数据倾斜】

Spark程序运行变慢&#xff0c;十有八九出现了数据倾斜。那么什么是数据倾斜、导致数据倾斜的原因以及如何克服它以保持Spark应用程序的最佳性能呢&#xff1f; 什么是数据倾斜&#xff1f; Apache Spark中的数据倾斜指的是&#xff0c;在处理的数据其在不同分区之间分布不均…

基于django的购物商城系统

摘要 本文介绍了基于Django框架开发的购物商城系统。随着电子商务的兴起&#xff0c;购物商城系统成为了许多企业和个人创业者的首选。Django作为一个高效、稳定且易于扩展的Python web框架&#xff0c;为开发者提供了便捷的开发环境和丰富的功能模块&#xff0c;使得开发购物商…

spring security 防止已经完成认证的会话再次访问login页面

版本 spring-security:6.2.1 方案 添加过滤器检查会话认证信息&#xff0c;如果包含非匿名用户的认证信息则调用认证成功处理器 final static String URL_LOGIN "/login"; final AuthenticationSuccessHandler authenticationSuccessHandler new SavedRequestA…

GEE入门篇|遥感专业术语(实践操作3):时间分辨率(Temporal Resolution)

目录 时间分辨率&#xff08;Temporal Resolution&#xff09; 1.Landsat 2.Sentinel-2 时间分辨率&#xff08;Temporal Resolution&#xff09; 时间分辨率是指特定传感器图像流的重访时间或时间节奏&#xff0c;重访时间是指卫星连续访问地球表面同一位置…

小迪安全30WEB 攻防-通用漏洞SQL 注入CTF二次堆叠DNS 带外

#知识点&#xff1a; 1、数据库堆叠注入 根据数据库类型决定是否支持多条语句执行 2、数据库二次注入 应用功能逻辑涉及上导致的先写入后组合的注入 3、数据库 Dnslog 注入 解决不回显(反向连接),SQL 注入,命令执行,SSRF 等 4、黑盒模式分析以上 二次注入&…

创作纪念日:记录我的成长与收获

机缘 一开始是在我深入学习前端知识的Vue.js框架遇到了一个问题&#xff0c;怎么都解决不了&#xff0c;心烦意乱地来csdn上找解决方法。开心的是真被我找到了&#xff0c;真的很感恩&#xff0c;也意识到在这个平台上分享自己的经验是多么有意义的事情&#xff0c;可能随便的…

BRIA.AI开源最强AI一键抠图模型RMBG,超简上手体验

本文首发: AIWalker 欢迎关注AIWalker&#xff0c;近距离接触底层视觉与基础AI技术 近日&#xff0c;BRIA.AI团队于HuggingFace开源了一个基于ISNet背景移除模型RMBG-1.4&#xff0c;它可以有效对前景与背景进行分离。RMBG-1.4在精心构建的数据集上训练而来&#xff0c;该数据包…

C++指针变量的引用

C中的指针变量引用是指使用引用来访问指针所指向的内存地址中的值。这种技术可以简化对指针所指向的对象的访问&#xff0c;并提高代码的可读性。 要创建指针变量的引用&#xff0c;可以使用以下语法&#xff1a; int* ptr; // 声明一个指针变量 int*& ref ptr; // 创建…