算法·动态规划Dynamic Programming

在这里插入图片描述

 很多人听到动态规划或者什么dp数组了,或者是做到一道关于动态规划的题目时,就会有一种他很难且不好解决的恐惧心理,但是如果我们从基础的题目开始深入挖掘动规思想,在后边遇到动态规划的难题时就迎难而解了。
 其实不然,动态规划类题目确实很不好发现解题思路,如何成为高手呢?那就是多刷题,但是还是像上边所说,不要刚接触就上难题只会让自己望而却步,我们只有不断积累经验,才能完全拿捏动态规划里面的精髓。
动态规划算法思想的核心
 有资料这样解释动态规划过程:
将问题分解为多个阶段,每个阶段对应一个决策,我们可以记录每个阶段所得到的状态的集合(去掉重复的),然后根据当前的状态的集合推导出下一阶段的状态集合,动态的向前推进,直至到达最终阶段,得到我们想要的结果。
有一位博主很巧妙地说明了上边所说的含义

A:问“1+1+1+1+1+1+1+1+1”等于几?
B:等于9

A:在表达式前边加上1+后结果等于几?
B:等于10(迅速)
A:为什么这么快得到结果呢?
B:因为我已经知道1+1+1+1+1+1+1+1+1等于9,再加1就得到10了
,而不是重新再算一遍。

 ok,了解了动态规划的思想,我们就可以来探寻遇到动态规划的问题时,我们应该怎么求解

首先,用一个简单的dp问题进行入门
第N个泰波那契数
在这里插入图片描述
在做每道题时,我们首先要来解析题目
 我们已经知道第0,1,2个数的值了,我们再来看后边的表达式,很明显,有了前边的值还有了这个表达式,我们可以求出任意n位置的值。
我们可以用动态规划的分析方法来分析这道题目

  1. 状态表示
    我们可以创建一个数组
    在这里插入图片描述
     这个数组就叫做dp表,我们所要做的就是把dp表中的数填满,然后就可以得到我们的最终结果。
     那么状态表示是什么意思呢?
     状态表示就是dp[i]位置上元素的含义,dp[i]在这道题目中就表示第i个泰波那契数的值。
     这道题目的状态表示很容易就能看出来,但遇到难题后,会遇到其他状态表示的方式,有一些会比较抽象,但是我们可以从浅入深,慢慢来嘛。

  2. 状态转移方程
     这道题目的状态转移方程题目已经给出来咯,如果后边遇到某些题没给呢?那当然是自己找规律,很简单的。

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

  1. 初始化
     如果知道了状态转移方程,然而不知道如何求出dp数组中的元素,那不就是没用嘛,所以前边给出我们前三个状态的值,由此我们才可以知道第四个,第五个的值。
     所以初始化还是很重要的,根据题目所示,将我们构建出的数组进行初始化。
  2. 填表顺序
     这道题的填表顺序明显是从左到右,知道了前边数组的元素,我们才能根据状态转移方程来求解右边的值。
    你是怎么知道填表顺序的呢?
     确定填表顺序,为了填写当前状态表示的时候,所需要的状态已经计算过了。就像这道题,我们要求dp[4]的值,然而dp[3]我们都不知道,怎么求dp[4]呢?
  3. 返回值
     根据状态表示,我们知道返回i位置的元素就是第i个泰波那契数,所以我们直接返回i即可
    现在我们就可以开始上手写代码
    写代码也是有几个步骤需要注意
class Solution {
public:
    int tribonacci(int n) {
        //创建dp表
        int* dp=new int[n+1];
        //边界问题
        if(n==0||n==1)
        {
            return n;
        }
        if(n==2)
        {
            return 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];
    }
};
  1. 观察是否可以优化
    在这里插入图片描述

 可以看出,我们内存消耗比较大,这是因为传入多少个n我们就开了n个空间,时间复杂度为O(N),空间复杂度也是O(N)。
 这道题我们可以优化空间复杂度,我们在进行计算时,可以使用几个变量循环往复存储dp的值,这样只需要常熟级别的空间消耗就解决了这个问题。
如图
在这里插入图片描述
 然后我们就可以得出第四个结果的值为2,此时更新a,b,c中的值
在这里插入图片描述
优化后代码如下

class Solution {
public:
    int tribonacci(int n) {
        //创建dp表
        int a,b,c,sum;//定义四个变量
        //边界问题
        if(n==0||n==1)
        {
            return n;
        }
        if(n==2)
        {
            return 1;
        }
        //初始化
        a=0;
        b=1;
        c=1;
        //填表
        for(int i=3;i<=n;i++)
        {
            sum=a+b+c;
            a=b;
            b=c;
            c=sum;
        }
        return sum;
    }
};

 这道题我们会提到空间优化,后续中关于空间优化我们就不再多说了,重要的是能通过这道题,对吧!
接下来就是一道我们再熟悉不过的一种题目了,上楼梯问题
来看第一道关于上楼梯的题目
三步问题

在这里插入图片描述
使用上边同样的分析步骤

  1. 状态表示

 这道题目和上一道就已经截然不同了,我们需要自己进行分析,这道题目中,我们开出的dp表第i个位置的元素,表示的是到达第i位置台阶有多少种方法。

  1. 状态转移方程

 小孩依次可以上1,2,3节台阶,我们可以一个一个进行分析,为什么这里状态表示是小孩到达该台阶所有的走法。
在这里插入图片描述

 小明上第一节台阶,显而易见,只有一种方法,就是直接跳上去.
 小明上第二个台阶时,我们就可以进行选择了,首先,我们可以先到第一个台阶,再从第一个台阶到第二个台阶,也可以直接到第二个台阶。
 怎么上第三个台阶呢?我们可以先到第一个台阶,再从第一个台阶直接到第三个台阶,到第一个台阶有一种方法,也可以先到第二个台阶,然后从第二个台阶到第三个台阶,到达第二个台阶有两种方法,所以到达第3个台阶有1+1+2中方法。
在这里插入图片描述

 从状态表示我们可以看出,小孩一次可以走三步,dp[i]就等于我们先到dp[i-1]的方法数量加上dp[i-2]加dp[i-3].
 所以状态表示方程为dp[i]=dp[i-1]+dp[i-2]+dp[i-3]
3. 初始化
 通过上边的分析,我们已经知道了爬前三节楼梯可以有的方法数目,不再赘述。

  1. 确定填表顺序
     和前边拿到题目一样,填表顺序必须能帮助我们完成,在想要求出某个值时,所需要的元素都是已知的。
  2. 返回值
     通过状态表示可以知道,返回dp[n]即返回到达第n节台阶时有多少种上楼梯的方法。

ok,有了上述的分析,我们就可以上手写代码了。

class Solution {
public:
    int waysToStep(int n) {
        //开dp表
        int* dp=new int[n+1];
        //边界调节
        if(n==1||n==2)
        {
            return n;
        }
        if(n==3)
        {
            return 4;
        }
        //初始化
        dp[1]=1;
        dp[2]=2;
        dp[3]=4;
        //填dp表
        for(int i=4;i<=n;i++)
        {
            dp[i]=dp[i-1]+dp[i-2]+dp[i-3];
        }
        return dp[n];
    }
};

运行后
在这里插入图片描述
 这个报错的含义就是计算结果超出了int的数据范围,这是因为我们忽略了题目中所说要对结果取模。
在这里插入图片描述
第三道题
使用最小花费爬楼梯
在这里插入图片描述

 这道题目还是一个爬楼梯的题目,一次可以爬两节,和上到题目不同的是,我们不需要计算方法数,而是在爬每节楼梯索要花费的最小花费,用同样的方法对象这道题目进行解析。
 但是这次我们可以选择从两个方面对这道题目进行分析

  • 第一种
  1. 状态表示

 有了上边的经验,这道题目我们同样可以知道,同样创建一个dp表,dp[i]表示到达i台阶花费的最少的money。
2. 状态转移方程
 我们一步一步进行分析,首先我们要知道cost第一个元素为台阶是0的台阶要花费的money,我们可以选择从第0个或者第1个台阶开始往上。 我们要想知道到达i位置所花费的最小数目,要到达第i阶只有两种方法,就是从第i-1位置或者第i-2位置往上跑过去的,所以我们要知道dp[i-1]加上第i-1位置的费用,还要知道dp[i-2]加上i-2台阶的费用。
所以我们的撞他转移方程为

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

  1. 初始化
     初始化保证填表的时候不会越界。我们要到楼梯顶部,而cost数组是从0下标开始的,所以我们所要到达的楼顶是dp[n],n就是cost的大小。
    这道题为什么没有边界判断呢?
     这是因为数据范围是大于等于2的。我们起始就在0或者1号台阶上,所以dp[0]=0,dp[1]=0.
  2. 确定填表顺序
     我们想要知道爬到后边台阶花费的最少费用,就需要知道爬到他前边两个台阶所花费的最少费用加上从这个台阶向上爬所需要的费用。所以填表顺序是从左往右。
  3. 返回值
     返回值就是dp[i],i就是楼梯顶部需要的最低花费。要注意,顶楼并不是cost数组最后一个位置,cost最后的元素表示距离楼顶前的台阶往上爬所要消费的money。
    ok,接下来我们就可以上手写代码了。流程基本不变。
class Solution {
public:
    int minCostClimbingStairs(vector<int>& cost) {
        int* dp=new int[cost.size()+1];
        dp[0]=0;
        dp[1]=0;
        for(int i=2;i<=cost.size();i++)
        {
            dp[i]=min(dp[i-1]+cost[i-1],dp[i-2]+cost[i-2]);
        }
        return dp[cost.size()];
    }
};

接下来我们来换一种思路去求解这道题。

  1. 状态表示
    dp[i]表示从i位置出发,到达楼顶需要的最少花费。和前边的思路不同的是,我们这次从前向后递进。
    在这里插入图片描述

  2. 状态转移方程
    在这里插入图片描述
     很明显,我们应该选出两者之中小的那个,正如状态表示所说,dp[i]表示的是从该位置到达楼顶的最小花费。如果从i+1位置到达楼顶花费比从i+2位置到达楼顶花费少的话,我们当然要选择从i+1位置到达楼顶。
    状态转移方程为

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

  1. 初始化
     此时,根据状态方程,我们应该从右向左初始化,即初始化后边的值,dp表示从某位置出发到达楼顶位置,假设我们初始化的dp表为dp[n]。
    在这里插入图片描述
     其中这里的n是cost.size()。表示的就是楼顶的位置,楼顶我们就不用初始化了,不管怎样都是0,dp[n-1]就是cost[cost.size()-1]。

  2. 确定填表顺序
     填表顺序就是保证想要知道某位置的元素时,所需要的计算过程中的元素是已知的,所以这道题就是从右向左填表。

  3. 返回值
     根据状态表示可知从i=0位置到达楼顶的花费和i=1位置到达楼顶的花费的较小值。
    代码如下

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

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

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

相关文章

数据结构与算法3-选择排序

文章目录 1. 认识选择排序2. 图示2.1 图示12.2 图示2 3. 代码 1. 认识选择排序 双层for循环&#xff0c;每次选出最小的数放到i位置&#xff0c;时间复杂度O( n 2 n^2 n2)&#xff0c;空间复杂度O(1);从未排序的序列中找到最小&#xff08;或最大&#xff09;的元素&#xff0…

【CNN轻量化】ParameterNet: Parameters Are All You Need 参数就是你所需要的

论文链接&#xff1a;http://arxiv.org/abs/2306.14525 代码链接&#xff1a;https://github.com/huawei-noah/Efficient-AI-Backbones 一、摘要 现有的低FLOPs模型&#xff08;轻量化模型&#xff09;无法从大规模预训练中受益。本文旨在增加大规模视觉预训练模型中的参数数量…

程序员的最佳副业居然是炒股

前言 之前的文章 《程序员的最佳副业居然是这个》讲述了个人的副业选择&#xff0c;和各种做过的副业。最后选择了炒股。那么究竟是否能够在股市里赚到利润呢&#xff1f;以我个人最近的交易记录来看&#xff0c;答案是肯定的。 一个半月赚取了 2898 程序员投资的优势 程序…

netty基础_12.用 Netty 自己实现简单的RPC

用 Netty 自己实现简单的RPC RPC 基本介绍我们的RPC 调用流程图己实现 Dubbo RPC&#xff08;基于 Netty&#xff09;需求说明设计说明代码封装的RPCNettyServerNettyServerHandlerNettyClientHandlerNettyClient 接口服务端(provider)HelloServiceImplServerBootstrap 客户端(…

概率基础——逻辑回归多分类法

概率基础——逻辑回归多分类法 逻辑回归是一种经典的分类算法&#xff0c;通常用于解决二分类问题。然而&#xff0c;在实际应用中&#xff0c;我们经常会遇到多分类任务。本文将简单介绍逻辑回归的理论、多分类方法以及优缺点&#xff0c;并提供一个Python实现的示例。 逻辑…

【MySQL】图形化界面工具DataGrip安装&配置&使用

前言 大家好吖&#xff0c;欢迎来到 YY 滴MySQL系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过C Linux的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY的…

npm出现内部错误,重新设置镜像

问题&#xff1a; 报错解释&#xff1a; 这个错误表明你尝试从一个指定的npm镜像源的响应时失败了。可能的原因包括网络问题、镜像源不可用、DNS解析问题或者镜像源的确已经下线或更改。 1.重新设置镜像源 设置淘宝镜像源&#xff1a; npm config set registry https://re…

网络面试——http 和 https 的区别

区别&#xff1a; 1. HTTP 是超文本传输协议&#xff0c;信息是明文传输&#xff0c;HTTPS 是具有安全性的 SSL 加密传输协议。HTTPS 是由 SSL HTTP 协议构建的可进行加密传输、身份认证的网络协议&#xff0c;比 HTTP 协议安全 2. 端口号&#xff1a;http 使用 80 端口&#…

ros小问题之差速轮式机器人轮子不显示(rviz gazebo)

在rviz及gazebo练习差速轮式机器人时&#xff0c;很奇怪&#xff0c;只有个机器人的底板及底部的两个万向轮&#xff0c;如下图&#xff0c; 后来查看相关.xacro文件&#xff0c;里面是引用包含了轮子的xacro文件&#xff0c;只需传入不同的参数即可调用生成不同位置的轮子&…

代码学习第24天----回溯算法

随想录日记part24 t i m e &#xff1a; time&#xff1a; time&#xff1a; 2024.03.10 主要内容&#xff1a;回溯算法在代码学习中尤其重要&#xff0c;所以今天继续加深对其的理解&#xff1a;1&#xff1a;递增子序列 &#xff1b;2.全排列 &#xff1b;3.全排列II 491.递…

MySQL基础-----事务(下)

目录 前言 一、并发事务问题 1.赃读 2.不可重复读 3.幻读 二、事务隔离级别 1.相关操作 2.案例演示 前言 本期我们继续上一期事务的内容&#xff0c;本期的主要讲解的是并发事务的相关问题以及解决方式&#xff0c;内容可能会比较难去理解&#xff0c;不过我会尽量详细说…

C++ UML类图

参考文章&#xff1a; &#xff08;1&#xff09;C UML类图详解 &#xff08;2&#xff09;C基础——用C实例理解UML类图 &#xff08;3&#xff09;C设计模式——UML类图 &#xff08;4&#xff09;[UML] 类图介绍 —— 程序员&#xff08;灵魂画手&#xff09;必备画图技能之…

31.HarmonyOS App(JAVA)鸿蒙系统app Service服务的用法

鸿蒙系统app Service服务的用法 后台任务调度和管控 HarmonyOS将应用的资源使用生命周期划分为前台、后台和挂起三个阶段。前台运行不受资源调度的约束&#xff0c;后台会根据应用业务的具体任务情况进行资源使用管理&#xff0c;在挂起状态时&#xff0c;会对应用的资源使用进…

《C语言深度剖析》---------关键字(1)

1.双击实质--->加载内存 windows系统里面&#xff0c;双击的本质就是运行程序&#xff0c;把程序加载到内存里面&#xff1b; 任何程序运行的时候都必须加载到内存里面&#xff1b; 程序没有运行之前在硬盘里面&#xff0c;为什么程序运行之前必须加载到内存里面呢&#…

Spring Web MVC入门(5)

响应 在我们前面的代码例子中, 都已经设置了响应数据Http响应结果可以是数据, 也可以是静态页面, 也可以针对响应设置状态码, Header信息等. 返回静态页面 创建前端页面index.html(注意路径) html代码如下: <!DOCTYPE html> <html lang"en"> <hea…

Nutanix 国产化替代|一文了解 SmartX 超融合替代可行性与迁移方案

2022 年 8 月 19 日&#xff0c;Nutanix&#xff08;路坦力&#xff09;宣布中国市场自 2023 财年起将转型为合作伙伴销售主导模式&#xff0c;引起了广泛关注&#xff1b;同时结合当前 IT 基础架构的国产化趋势背景&#xff0c;不少正在使用和考虑使用 Nutanix 产品的企业开始…

Vue 中预加载组件

在 Vue 中&#xff0c;利用 VueRouter 可以轻松的实现两个组件&#xff08;页面&#xff09;之间的切换&#xff0c;有个常用的设计就是需要在登录页登录后跳转至一个内容页&#xff0c;通常的做法是在登录校验完成之后立即切换路由至内容页&#xff0c;接着内容页发送网络请求…

极简生活|2024年让自己越来越好的18个极简好习惯

哈喽&#xff0c;你好啊&#xff0c;我是雷工&#xff01; 转眼间已经进入了2024年&#xff0c;新的一年&#xff0c;新的开始。 俗话说&#xff1a;百尺高台起于垒土&#xff0c;千里之堤毁于蚁穴。 好习惯积累的越多&#xff0c;坏习惯越来越少&#xff0c;我们的生活才能越…

Iterator对象功能学习

package config;import java.util.Iterator; import java.util.Properties; import java.util.Set;/*** 这个类演示了如何使用Properties类来存储和访问键值对。* Properties类继承自Hashtable&#xff0c;因此它可以用来存储键值对数据&#xff0c;且支持同步。*/ public clas…

MySQL介绍

一、MySQL数据库介绍 1、发展史 1996年 MySQL1.0 2008年1月16日 Sun公司收购了 MySQL 2009年4月20日 Oracle收购了Sun公司 MySQL是一种开放源代码的关系型数据库管理系统 使用最常用的数据库管理语言 SQL&#xff08;结构化查询语言&#xff09; MySQL是开放源代码的 因此所有…
最新文章