【二】【C语言\动态规划】解码方法、不同路径、不同路径II,三道题目深度解析

动态规划

动态规划就像是解决问题的一种策略,它可以帮助我们更高效地找到问题的解决方案。这个策略的核心思想就是将问题分解为一系列的小问题,并将每个小问题的解保存起来。这样,当我们需要解决原始问题的时候,我们就可以直接利用已经计算好的小问题的解,而不需要重复计算。

动态规划与数学归纳法思想上十分相似。

数学归纳法:

  1. 基础步骤(base case):首先证明命题在最小的基础情况下成立。通常这是一个较简单的情况,可以直接验证命题是否成立。

  2. 归纳步骤(inductive step):假设命题在某个情况下成立,然后证明在下一个情况下也成立。这个证明可以通过推理推断出结论或使用一些已知的规律来得到。

通过反复迭代归纳步骤,我们可以推导出命题在所有情况下成立的结论。

动态规划:

  1. 状态表示:

  2. 状态转移方程:

  3. 初始化:

  4. 填表顺序:

  5. 返回值:

数学归纳法的基础步骤相当于动态规划中初始化步骤。

数学归纳法的归纳步骤相当于动态规划中推导状态转移方程。

动态规划的思想和数学归纳法思想类似。

在动态规划中,首先得到状态在最小的基础情况下的值,然后通过状态转移方程,得到下一个状态的值,反复迭代,最终得到我们期望的状态下的值。

接下来我们通过三道例题,深入理解动态规划思想,以及实现动态规划的具体步骤。


91. 解码方法

题目解析

我们先把动态规划五个步骤抄过来。

  1. 状态表示:

  2. 状态转移方程:

  3. 初始化:

  4. 填表顺序:

  5. 返回值:

状态表示

动态规划思想和数学归纳法相似,由一个简单情况下的解,通过状态转移方程,推导出其他的状态,最后返回我们希望得到的状态。

状态表示通常由经验加题目得到。

经验是指,以某个位置为结尾,或者以某个位置为开头。

题目给我们一个字符串,需要我们返回解码方法的总和。

我们可以定义,dp[i]表示从0下标字符开始,一直到i下标字符结尾,这段字符串的解码方法数。

状态转移方程

我们想一想dp[i]能不能由其他的状态推导得出来,dp[i]与其他状态的联系是什么?

我们可以单独考虑i下标字符的状态,在0下标字符一直到i下标字符,这段字符串我们称为s,s字符串中,i下标字符的状态是什么?

i下标字符肯定是0-9其中之一,如果我们要对s字符串进行解码,i字符要么单独解码,要么和i-1下标字符结合一起解码,对于i下标字符一共就这两种情况。

如果是单独解码情况,i一定是1-9其中之一,0不能单独解码。

如果是与i-1下标字符结合解码,那么i-1与i组合一定是10-26的数字,对于i-1数组不能是0,只能是1或者2。

根据这两种情况我们就可以得到状态转移方程,

如果s[i]!='0',那么我们就考虑单独解码情况,如果单独解码,他的解码数就是dp[i-1]。

dp [i-1]是0-(i-1)之间的解码总数,对于这些解码的每一条情况,我们都可以在末尾添加对i单独解码的步骤,所以i单独解码情况下的解码数就是dp[i-1]。

如果10<=(s[i-1]-'0')*10+s[i]-'0'<=26,那我们就考虑与i-1结合解码情况,他的解码数就是dp[i-2]。

dp[i-2]是0-(i-2)之间的解码总数,对于这些解码的每一条情况,我们都可以在末尾添加i-1与i结合解码的情况,所以结合解码情况下的解码数就是dp[i-2]

我们要求的是i的解码总数,所以需要把这两种情况的解码数加上。

状态转移方程,即:

if(s[i]!='0') dp[i]+=dp[i-1];

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

初始化

初始化就是确定最小的基础解,然后由状态转移方程,以最基础的解推导出其他的解,最后返回我们希望得到的解。

由状态转移方程,我们求dp[i]需要dp[i-1]和dp[i-2]的值,所以我们需要初始化前两个数据。

在这道题中,我们发现初始化前两个数据的值,这个过程的实现其实也挺复杂的,我们希望有更加简便的初始化方式。

对于这种情况,我们通常可以在dp数组中添加虚拟节点。

在原先下标0前面添加两个虚拟结点,这样我们就可以正常从0开始遍历,也不会产生越界的情况。

如果要实现,就需要把原数据全部往后移2个位置,也就是我们的dp状态表示需要修正一下,定义dp[i]对应s数组[i-2]位置的元素,从第一个元素开始到s数组中i-1下标元素,这段字符串中的解码总数。

需要注意的两个点,1.虚拟结点里面的值,不能影响其他值的正常求解。

2.注意dp数组与s数组的对应关系。

if(s[i-2]!='0') dp[i]+=dp[i-1];

if(10<=(s[i-3]-'0')*10+s[i-2]-'0'<=26) dp[i]+=dp[i-2];

注意这段代码的改动。dp表下标与s表下标的对应关系。

如果我们要填第一个值,同样考虑两种情况,单独解码或者与前面数结合解码。

单独解码,如果不为0,单独解码情况解码数是1,所以1下标初始化为1.

结合解码,不存在,结合解码解码数是0,所以0下标初始化为0。

对应状态转移方程,不能有影响。

所以初始化,即:

dp[0]=0,dp[1]=1;

填表顺序

从左往右

返回值

dp[n],返回最后一个元素即可。

代码实现

 

int numDecodings(char* s) {int n=strlen(s);int dp[n+2];memset(dp,0,sizeof(dp));dp[0]=0;dp[1]=1;for(int i=2;i<n+2;i++){if(s[i-2]!='0') dp[i]+=dp[i-1];if(i-3>=0){int t=(s[i-3]-'0')*10+(s[i-2]-'0');if(t>=10&&t<=26) dp[i]+=dp[i-2];}else{dp[i]+=dp[i-2];}}return dp[n+1];
}

首先用n变量接收s数组的数组大小。

多创建两个空间大小,前两个空间是虚拟节点,作用是统一每一个状态的填写、遍历。

初始化前两个虚拟节点的数据。

从i=2开始遍历,一直到最后一个位置。

dp[i]表示s[0]--s[i-2]字符串的编码数。


62. 不同路径

题目解析

机器人每次只能往右边走一步,或者往下边走一步,最后到达网格的右下角,求一共有多少条不同的路径。

状态表示

我们可以创建这样的dp表,dp[i][j]表示从第1行,第1列出发,到达第i行,第j列时的不同路径数。

状态转移方程

我们想一想dp[i][j]的状态能不能由其他的状态推导得出。

针对于dp[i][j]如果我们要走到i,j位置,要么从(i-1,j)往下走一步,要么从(i,j-1)往右走一步,这两种情况。

对于第一种情况,dp[i-1][j]表示从(1,1)出发,到达(i-1,j)的路径数,对于每一条路径最后都添加一步,往下走一步,到达(i,j),这样的路径就是从(1,1)出发到达(i,j)的路径,这样的路径有多少条呢?一共就是dp[i-1][j]条。

对于第二种情况,同理,一共有dp[i][j-1] 条。

所以dp[i][j]=dp[i-1][j]+dp[i][j-1]。

回想一下数学归纳法,我们知道最小的基础解,知道每一相邻状态的推导,是否可以从最小的基础的状态依次推导出我们希望得到的状态?答案是肯定的。

初始化

我们需要把dp表中第一行到第三行,第一列到第七列中所有的空都填上。

我们可以从(1,1)位置开始遍历,这样我们就做到了统一,统一的意思是dp表中所有有意义的状态,都可以通过状态转移方程推导得到,这样推导出(1,1)位置我们就需要初始化前驱。

由状态转移方程知,dp[i][j]=dp[i-1][j]+dp[i][j-1]

我们需要使得dp[0][1]+dp[1][0]的值是1,可以把数组全部置0,然后选一个(0,1)(1,0)选一个位置置1就可以了。

这样我们就可以通过状态转移方程推导出(1,1)

接着我们看(1,1)能不能一直推导下去。

推导(i,j)需要(i,j-1)(i-1,j)位置的状态。

如果我们要得到第一行的值,需要第零行的状态,而我们初始化为0,是否会影响推导的结果?想一想状态转移方程的意义,要么从上面往下走一步,要么从左边往右走一步,上面没有路可以走,就表示没有这个路径,所以上面的路径数是0,不会影响结果。那么推导第一行是不会出问题的。

同理推导第一列的时候,我们需要第零列的状态值,同理,置0是没有问题的。

这样我们就可以通过已经初始化的值,推导出所有状态。

所以初始化为:

全部置0,然后

dp[0][1]=1 dp[1][0]=0

或者

全部置0,然后

dp[1][0]=1,dp[0][1]=0

填表顺序

从左往右,从上往下

返回值

返回dp[m][n],dp[i][j]表示从第1行,第1列出发,到达第i行,第j列时的不同路径数。

代码实现

 
int uniquePaths(int m, int n) {int dp[m+1][n+1];for(int i=0;i<=m;i++){memset(dp[i],0,sizeof(dp[i]));}dp[0][1]=1;for(int i=1;i<=m;i++){for(int j=1;j<=n;j++){dp[i][j]=dp[i-1][j]+dp[i][j-1];}}return dp[m][n];}

代码实现的步骤:

  1. 创建dp表

  2. 初始化

  3. 填表

  4. 返回值


63. 不同路径 II

题目解析

状态表示

我们可以统一遍历每一个dp状态,创建虚拟节点,也可以直接初始化,不创建虚拟节点,上一道题目我们创建了虚拟节点,对状态推导进行统一化,这道题我们就不创建虚拟节点,直接初始化。

此时我们可以定义dp[i][j]表示从(0,0)开始到达(i,j)位置不同路径的方法数。

状态转移方程

对于dp[i][j]位置的状态,首先(i,j)位置要么没有障碍物,要么有障碍物。

如果没有障碍物,那我们就要考虑(i,j)的状态值,想一想其他状态然后推导出该状态,要么从上面往下走一步到(i,j)要么从左边往右走一步到(i,j),所以dp[i][j]=dp[i][j-1]+dp[i-1][j]

如果有障碍物,说明我们没办法到达这个位置,所以到达这个位置的方法数就是0,我们置0即可。

所以状态转移方程为:

if(ob[i][j]==0)dp[i][j]=dp[i][j-1]+dp[i-1][j]

if(ob[i][j]==1) dp[i][j]=0

初始化

由状态状态转移方程我们知道,需要求(i,j)位置的状态,我们需要(i,j-1)和(i-1,j)位置的状态,所以我们需要初始化第一行和第一列的元素。

对于第一行和第一列,如果我们可以到达,方法数一定是1,很容易可以得出,如果我们遇到一个位置不能到达,那么他后面的位置也不能到达。

所以我们把所有位置初始化为0,能到达的位置值1即可。

故初始化:

 
    for(int i=0;i<row;i++){memset(dp[i],0,sizeof(dp[i]));}for(int j=0;j<col;j++){if(ob[0][j]==1){break;}dp[0][j]=1;}for(int i=0;i<row;i++){if(ob[i][0]==1){break;}dp[i][0]=1;}

填表顺序

从左往右,从上往下

返回值

返回最后一个元素的位置,即dp[row-1][col-1]

代码实现

 
int uniquePathsWithObstacles(int** ob, int obstacleGridSize, int* obstacleGridColSize) {int row=obstacleGridSize;int col=obstacleGridColSize[0];int dp[row][col];for(int i=0;i<row;i++){memset(dp[i],0,sizeof(dp[i]));}for(int j=0;j<col;j++){if(ob[0][j]==1){break;}dp[0][j]=1;}for(int i=0;i<row;i++){if(ob[i][0]==1){break;}dp[i][0]=1;}for(int i=1;i<row;i++){for(int j=1;j<col;j++){if(ob[i][j]==0){dp[i][j]=dp[i-1][j]+dp[i][j-1];}}}return dp[row-1][col-1];
}
 
int col=obstacleGridColSize[0];

这条代码可以得到列数。

 
int dp[row][col];for(int i=0;i<row;i++){memset(dp[i],0,sizeof(dp[i]));}for(int j=0;j<col;j++){if(ob[0][j]==1){break;}dp[0][j]=1;}for(int i=0;i<row;i++){if(ob[i][0]==1){break;}dp[i][0]=1;}

初始化操作,dp表与ob数组一一对应。

 
    return dp[row-1][col-1];

返回值。


结尾

今天我们学习了动态规划的思想,动态规划思想和数学归纳法思想有一些类似,动态规划在模拟数学归纳法的过程,已知一个最简单的基础解,通过得到前项与后项的推导关系,由这个最简单的基础解,我们可以一步一步推导出我们希望得到的那个解,把我们得到的解依次存放在dp数组中,dp数组中对应的状态,就像是数列里面的每一项。最后感谢您阅读我的文章,对于动态规划系列,我会一直更新,如果您觉得内容有帮助,可以点赞加关注,以快速阅读最新文章。

最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。

同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。

谢谢您的支持,期待与您在下一篇文章中再次相遇!

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

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

相关文章

CSS3多列分页属性

CSS3多列 Firefox浏览器支持该属性的形式是-moz-column-count&#xff0c;而基于Webkit的浏览器&#xff0c;例如Safari和Chrome&#xff0c;支持该属性的形式是-webkit-column-count column-count&#xff1a;该属性定义多列文本流中的栏数 语法&#xff1a;column-count:int…

本地websocket服务端结合cpolar内网穿透实现公网访问

文章目录 1. Java 服务端demo环境2. 在pom文件引入第三包封装的netty框架maven坐标3. 创建服务端,以接口模式调用,方便外部调用4. 启动服务,出现以下信息表示启动成功,暴露端口默认99995. 创建隧道映射内网端口6. 查看状态->在线隧道,复制所创建隧道的公网地址加端口号7. 以…

「数据结构」二叉树2

&#x1f387;个人主页&#xff1a;Ice_Sugar_7 &#x1f387;所属专栏&#xff1a;初阶数据结构 &#x1f387;欢迎点赞收藏加关注哦&#xff01; 文章目录 &#x1f349;前言&#x1f349;链式结构&#x1f349;遍历二叉树&#x1f34c;前序遍历&#x1f34c;中序遍历&#x…

Qt 多线程用法

文章目录 开发平台QThread 类 moveToThreadQtConcurrent::run QFutureWatcherQThreadPool QRunnable 开发平台 项目说明OSwin10 x64Qt6.6compilermsvc2022构建工具cmake QThread 类 moveToThread 写一个简单的例子吧,比较容易理解,方便入门. 也可以看出这种方式,对于线程…

Polygon zkEVM Spearbit审计报告解读(2022年12月版本)

1. 引言 前序博客&#xff1a; Polygon zkEVM Hexens审计报告解读&#xff08;2022年12月至2023年2月版本&#xff09; 主要见&#xff1a; Polygon zkEVM Security Review: December 2022 Engagement Polygon zkEVM为提供&#xff08;opcode层面兼容的&#xff09;EVM等价…

Linux学习小结

目录结构 tree -L 1 / # /root #root用户的家目录 /home #存储普通用户家目录 lostfound #这个目录平时是空的&#xff0c;存储系统非正常关机而留下“无家可归”的文件 /usr #系统文件&#xff0c;相当于C:\Windows /usr/local #软件安装的目录&#xff0c;相当于C:\Progra…

Ubuntu-20.04.2 mate 上安装、配置、测试 qtcreator

一、从repo中安装 Ubuntu-20.04.2的repo中&#xff0c;qtcreator安装包挺全乎的&#xff0c;敲完 sudo apt install qtcreator 看一下同时安装和新软件包将被安装列表&#xff0c;压缩包252MB&#xff0c;解压安装后933MB&#xff0c;集大成的一包。 sudo apt install qtcrea…

使用Java语言解决古典猴子分桃问题

一、主要思想 五只猴子分桃 第一只猴子呀 平均分成五分 挤出来多一个 多的扔入海中 拿了其中一份 来了五只猴子 均是如此操作 第五只猴子呀 还存有多少只 二、基本代码 public class MonkeyPeach {public static void main(String[] args){int n 1;int m 0;int flag1;int…

uniapp如何原生app-云打包

首先第一步&#xff0c;需要大家在HBuilder X中找到一个项目&#xff0c;然后呢在找到上面的发行选项 发行->原生App-云打包 选择完该选中的直接大包就ok。 大包完毕后呢&#xff0c;会出现一个apk包&#xff0c;这是后将这个包拖动发给随便一个人就行了。 然后接收到的那…

【5G PHY】NR参考信号功率和小区总传输功率的计算

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…

初识Docker-什么是docker

Docker是一个快速交付应用、运行应用的技术 目录 一、Docker 二、运用场景 一、什么是Docker&#xff1f;它的作用是什么&#xff1f; Docker如何解决大型项目依赖关系复杂&#xff0c;不同组件依赖的兼容性问题? Docker允许开发中将应用、依赖、函数库、配置一起打包&…

MySQL中CASE when 实战

CASE 语法 CASEWHEN condition1 THEN result1WHEN condition2 THEN result2WHEN conditionN THEN resultNELSE result END; 将表中的内容转换为右边的形式&#xff1a; 1、创建表&#xff0c;创建数据 CREATE TABLEchapter10_7 (order_id VARCHAR(255) NULL,price VARCHAR(25…

阿里云经济型、通用算力型、计算型、通用型、内存型云服务器最新活动报价

阿里云作为国内领先的云计算服务提供商&#xff0c;提供了多种规格的云服务器供用户选择。为了满足不同用户的需求&#xff0c;阿里云推出了经济型、通用算力型、计算型、通用型和内存型等不同类型的云服务器。下面将详细介绍这些云服务器的最新活动报价。 一、阿里云特惠云服…

实验一传统的结构化的软件工程方法、实验二面向对象的软件工程、实验三软件测试

背景&#xff1a; 实验一 传统的结构化的软件工程方法 1实验目的 了解传统的软件工程方法的基本原理&#xff0c;掌握软件生命周期的全过程依次划分为需求分析、总体设计、详细设计、编码、测试、维护等几个重要阶段。每个阶段所要完成的任务以及提交的文档。 2实验内容 …

25、新加坡南洋理工、新加坡国立大学提出FBCNet:完美融合FBCSP的CNN,EEG解码SOTA水准![抱歉老师,我太想进步了!]

前言&#xff1a; 阴阳差错&#xff0c;因工作需要&#xff0c;需要查阅有关如何将FBCSP融入CNN中的文献&#xff0c;查阅全网&#xff0c;发现只此一篇文章&#xff0c;心中大喜&#xff0c;心想作者哪家单位&#xff0c;读之&#xff0c;原来是自己大导&#xff08;新加坡工…

OpenAI 官方 Prompt 工程指南:写好 Prompt 的六个策略

其实一直有很多人问我&#xff0c;Prompt 要怎么写效果才好&#xff0c;有没有模板。 我每次都会说&#xff0c;能清晰的表达你的想法&#xff0c;才是最重要的&#xff0c;各种技巧都是其次。但是&#xff0c;我还是希望发给他们一些靠谱的文档。 但是&#xff0c;网上各种所…

渗透实验 XSS和SQL注入(Lab3.0)

windows server2003IIS搭建 配置2003的虚拟机 1、利用AWVS扫描留言簿网站&#xff08;安装见参考文档0.AWVS安装与使用.docx&#xff09;&#xff0c;发现其存在XSS漏洞&#xff0c;截图。 2、 Kali使用beef生成恶意代码 cd /usr/share/beef-xss./beef执行上面两条命令 …

DBA-MySql面试问题及答案-上

文章目录 1.什么是数据库?2.如何查看某个操作的语法?3.MySql的存储引擎有哪些?4.常用的2种存储引擎&#xff1f;6.可以针对表设置引擎吗&#xff1f;如何设置&#xff1f;6.选择合适的存储引擎&#xff1f;7.选择合适的数据类型8.char & varchar9.Mysql字符集10.如何选择…

【优化】XXLJOB修改为使用虚拟线程

【优化】XXLJOB修改为使用虚拟线程 新建这几个目录 类&#xff0c; 去找项目对应的xxljob的源码 主要是将 new Thread 改为 虚拟线程 Thread.ofVirtual().name("VT").unstarted 以下代码是 xxljob 2.3.0版本 举一反三 去修改对应版本的代码 <!-- 定…