动态规划课堂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];
    }
}

结语:

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

 

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【QT 5 +Linux下软件生成+qt软件生成使用工具+学习他人文章+第一篇:使用linuxdeployqt软件生成】

【QT 5 Linux下软件生成qt软件生成使用工具学习他人文章第一篇&#xff1a;使用linuxdeployqt软件生成】 1、前言2、实验环境3、自我学习总结-本篇总结1、新手的疑问&#xff0c;做这件事的目的2、了解工具&#xff1a;linuxdeployqt工具3、解决相关使用过程中问题 4、参照文章…

5分钟轻松帮你EasyRecovery恢复女朋友照片

相信有不少男性电脑玩家都会将女朋友的照片存放在电脑硬盘之内&#xff0c;作为珍贵的收藏和回忆。但是在某些时候&#xff0c;如果我们错误地删除了这些照片&#xff0c;或者由于系统问题导致其中的照片丢失&#xff0c;那么我们怎么找回女朋友的照片&#xff1f;这个问题就足…

进程的学习

进程基本概念: 1.进程: 程序&#xff1a;存放在外存中的一段数据组成的文件 进程&#xff1a;是一个程序动态执行的过程,包括进程的创建、进程的调度、进程的消亡 2.进程相关命令: 1.top 动态查看当前系统中的所有进程信息&#xff08;根据CPU占用率排序&#xf…

微芒计划-简洁方便的效率待办管理工具【免费】

&#x1f632;微芒计划-简洁方便的效率待办管理工具【免费】 下载地址 &#x1f4dd;我的待办 快速添加待办任务&#xff0c;快速查看任务进度&#xff0c;摘要等。新增标签&#xff0c;分类&#xff0c;更好管理待办任务。 ☀️OKR目标管理 OKR让抽象的企业战略明确为上下对…

✅技术社区项目—Session/Cookie身份验证识别

session实现原理 SpringBoot提供了一套非常简单的session机制&#xff0c;那么它又是怎么工作的呢? 特别是它是怎么识别用户身份的呢? session又是存在什么地方的呢? 核心工作原理 借助cookie中的 JESSIONID 来作为用户身份标识&#xff0c;这个数据相同的&#xff0c;认…

车载电子电器架构 —— OEM基础技术概念开发流程

车载电子电器架构 —— 基础技术概念开发 我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 屏蔽力是信息过载时代一个人的特殊竞争力&#xff0c;任何消耗…

SpringMVC 学习(二)之第一个 SpringMVC 案例

目录 1 通过 Maven 创建一个 JavaWeb 工程 2 配置 web.xml 文件 3 创建 SpringMVC 配置文件 spring-mvc.xml 4 创建控制器 HelloController 5 创建视图 index.jsp 和 success.jsp 6 运行过程 7 参考文档 1 通过 Maven 创建一个 JavaWeb 工程 可以参考以下博文&#x…

吴恩达deeplearning.ai:Tensorflow训练一个神经网络

以下内容有任何不理解可以翻看我之前的博客哦&#xff1a;吴恩达deeplearning.ai 在之前的博客中。我们陆续学习了各个方面的有关深度学习的内容&#xff0c;今天可以从头开始训练一个神经网络了。 Tensorflow训练神经网络模型 我们使用之前用过的例子&#xff1a; 这个神经…

Python中的functools模块详解

大家好&#xff0c;我是海鸽。 函数被定义为一段代码&#xff0c;它接受参数&#xff0c;充当输入&#xff0c;执行涉及这些输入的一些处理&#xff0c;并根据处理返回一个值&#xff08;输出&#xff09;。当一个函数将另一个函数作为输入或返回另一个函数作为输出时&#xf…

JAVA算法和数据结构

一、Arrays类 1.1 Arrays基本使用 我们先认识一下Arrays是干什么用的&#xff0c;Arrays是操作数组的工具类&#xff0c;它可以很方便的对数组中的元素进行遍历、拷贝、排序等操作。 下面我们用代码来演示一下&#xff1a;遍历、拷贝、排序等操作。需要用到的方法如下 public…

26.HarmonyOS App(JAVA)列表对话框

列表对话框的单选模式&#xff1a; //单选模式 // listDialog.setSingleSelectItems(new String[]{"第1个选项","第2个选项"},1);//单选 // listDialog.setOnSingleSelectListener(new IDialog.ClickedListener() { // Override …

互联网加竞赛 机器视觉opencv答题卡识别系统

0 前言 &#x1f525; 优质竞赛项目系列&#xff0c;今天要分享的是 &#x1f6a9; 答题卡识别系统 - opencv python 图像识别 该项目较为新颖&#xff0c;适合作为竞赛课题方向&#xff0c;学长非常推荐&#xff01; &#x1f947;学长这里给一个题目综合评分(每项满分5分…

C++中的左值和右值

目录 一. 左值和右值的概念 1. 左值 1.1 可修改的的左值 1.2 不可修改的左值 右值 二. 左值引用和右值引用 1. 左值引用 2. 右值引用 主要用途 1. 移动语义 2. 完美转发 2.1 引用折叠 2.2 std::forward 一. 左值和右值的概念 什么是左值和右值 1. 左值 左值是一个表示…

Unity3D 使用 Proto

一. 下载与安装 这里下载Google Protobuff下载 1. 源码用来编译CSharp 相关配置 2. win64 用于编译 proto 文件 二. 编译 1. 使用VS 打开 2. 点击最上面菜单栏 工具>NuGet 包管理器>管理解决方案的NuGet 管理包 版本一定要选择咱们一开始下载的对应版本否则不兼容&am…

使用免费的L53巧解Freenom域名失效问题

进入2月份以来&#xff0c;不少小伙伴纷纷收到Freenom提供的域名失效&#xff0c;状态由正常变成了Pending。 失效后&#xff0c;域名无法使用&#xff0c;免费的午餐没有了&#xff0c;而现在域名的价格也是水涨船高&#xff0c;真是XXX。很多做外贸的小伙伴表示 难 啊&#x…

树状数组与线段树<2>——线段树初步

这个系列终于更新了(主要因为树状数组初步比较成功) 话不多说&#xff0c;切入正题。 什么是线段树&#xff1f; 线段树是一种支持单点修改区间查询(树状数组也行) and 区间修改单点查询(树状数组不行) and 区间修改区间查询(树状数组更不行)的高级数据结构&#xff0c;相当…

Chiplet技术与汽车芯片(二)

目录 1.回顾 2.Chiplet的优势 2.1 提升芯片良率、降本增效 2.2 设计灵活&#xff0c;降低设计成本 2.3 标准实行&#xff0c;构建生态 3.Chiplet如何上车 1.回顾 上一篇&#xff0c;我们将来芯粒到底是什么东西&#xff0c;本篇我们来看芯粒技术的优势&#xff0c;以及它…

5.1 Ajax数据爬取之初介绍

目录 1. Ajax 数据介绍 2. Ajax 分析 2.1 Ajax 例子 2.2 Ajax 分析方法 &#xff08;1&#xff09;在网页页面右键&#xff0c;检查 &#xff08;2&#xff09;找到network&#xff0c;ctrl R刷新 &#xff08;3&#xff09;找 Ajax 数据包 &#xff08;4&#xff09;…

多线程相关(4)

线程安全-下 使用层面锁优化减少锁的时间&#xff1a;减少锁的粒度&#xff1a;锁粗化&#xff1a;使用读写锁&#xff1a;使用CAS&#xff1a; 系统层面锁优化自适应自旋锁锁消除锁升级偏向锁轻量级锁重量级锁 ThreadLocal原理ThreadLocal简介原理ThreadLocal内存泄漏 HashMap…

VMware使用虚拟机,开启时报错:无法连接虚拟设备 0:0,因为主机上没有相应的设备。——解决方法

检查虚拟机配置文件并确保物理设备已正确连接。 操作&#xff1a; 选中虚拟机&#xff0c;打开设置&#xff0c;点击CD/DVD。在连接处选择使用ISO镜像文件