动态规划心得总结

1. 动态规划心得总结

1.1. 常规情况的基本步骤

  1. 给出递推函数定义
  2. 找出递推函数的递推关系
  3. 根据递推关系可以写出递归版本的代码
  4. 根据递推关系可以写出打表法的动态规划版本的代码

1.2. 给出递推函数的定义 & 找出递推关系

基本步骤中的第一步、第二步是紧密关联的。递推函数定义的不同会导致递推关系不同,甚至决定了在这种定义下能否找出对应的递推关系。当我们确认递推函数的定义后,那么其对应的递归关系也是确定的、唯一的(如果有的话)。

因此第一步很关键,这里也有些隐藏着的经验、细活,很多博客都不会说到,这是我自己思考总结的,包括我看的算法书上也没有写,都是根据题目直接给出了对应的递推函数定义,然后去论证怎么去得到递推关系,没有讲为什么要这样定义、是怎样想到这样的定义等。

一道题可能有多种递推函数的定义,但这些递推函数的有一个共同点就是:变量数量是一定的。

那么这个维度该怎么确认呢?总体看,这是一个活的过程,但也有些规律、经验可以依据。

首先看有几个输入,判断哪些输入是作为常量,哪些输入能够作为变量、或间接的贡献出变量。比如换钱的方法数,给了两个输入,一个是能用的币值list,一个是要换的钱。我们判断这两个值都能作为变量或间接贡献出变量:对于前者,我们会想到用多少中币值来计算,对于后者我们会想先从小钱开始,一步一步换到我们给定的钱数。再比如对于机器人到达指定位置的方法数这道题,给定了四个输入:N表示位置数、M表示起始位置、K表示使用的步数、P表示目标位置。我们可以看到N和M是常量,K、P作为变量。

其次,我们在上一步确定好能贡献变量的输入后,还要再选规划的方法,来确认每个输入能贡献多少变量。其实上一步是比较确定的,哪些输入是规划过程中的常量,哪些输入是规划过程中的变量,是比较明显的,容易确认的。但是规划的方法是多变的。一般来说确定在某一个输入上有几个在规划时用到的动点:比如对一个字符串来说,一般有两种选择:使用两个动点来表示区间的两端;使用一个动点来表示区间的一端,然后区间的另一端是固定死的。一个动点就对应的递推函数定义中的一个变量。

根据上面两步我们确认递推函数的维度。但在上面第二步,动点数量依赖于规划方法的选择,而规划方法就直接对应着递推函数的逻辑定义了。选规划方法最为关键重要,细活都在这。

1.2.1. 选择规划方法(重要)

在这一步需要跳出既有思维的约束,就是发现每一类规划方法中,能变的地方在哪,不同变化的一种组合就是具体的一种规划方法。

比如:对于字符串,我们进行区间规划的时候,区间有两端,这时我们面对的两种选择:在规划时,只固定一端,还是两端都动?如果固定一端的话,是固定哪一段,固定左端还是右端?然后我们还要确认,F(x)表示的是以x位置字符结尾(或开头)的xxx结果,还是说在【0,x】区间内,不限定以x位置字符结尾的xxx结果?

再比如,对于机器人到达指定位置的方法数,我们是把F(x,y)定义为机器人从位置x走y步到达常量P点的方法数,还是把F(x,y)定义成机器人从常量开始位置走x步到达位置y的方法数?这两种选择其实都是可以的。

再比如,对于龙与地下城游戏中,我们是把F(x,y)定义成从常量开始位置出发到达位置(x,y)所需的最小初始血量,还是说把F(x,y)定义成从变量位置(x,y)出发到达常量终点的所需最小初始生命值?对于这道题前者是行不通的,后者才可以。这就是个固定点选择导致的可行性与否。

......
......
......

在这个过程中要多尝试,一个错误观念是,从一开始就把自己第一想的到规划方法用上了,之后把所有时间放在尝试了去找递推关系上,就算找不到还是不换规划方法,然后就一直死磕着。。。

1.3. 根据递推关系写出动态规划版本代码

  1. 根据递推关系中值之间的依赖关系来确认是否能进行矩阵压缩。(可选)
  2. 首先生成dp矩阵,矩阵的维度数等于递推函数中的变量数(不包含常量,虽然常量也可能是用户输入给出,但它们仍然是常量,不是变量)
  3. 初始化dp矩阵:初始化dp矩阵和递归版本代码的递归终止条件对应,我们初始化dp矩阵主要是填一些边界值。
  4. 确认返回结果是啥(其实这一步也可以提到前面去)
  5. 根据递推关系中值与值之间的依赖关系确认打表的顺序、方向。
  6. 根据打表的顺序、方向、dp矩阵的维度、是否使用矩阵压缩来写循环。

1.4. 其他

上面说的是常规性的,一般性的步骤流程。但问题是多变的,需要具体分析。

比如:遇到的问题使用上面的步骤搞不定,像最大矩阵系列问题。信封嵌套问题等。
最大矩阵系列问题缺在完全放弃了直观的建模,生搬硬套常规的做法,信封嵌套问题属于要先对输入处理下,然后转为其他问题的解法。

反正动态规划都会用到辅助矩阵,辅助矩阵怎么定义、怎么用就是展现个人能力的地方,要充分发挥想象。
比如最长递增子序列一题用到了一个记录了子序列长度到其结尾最小数映射关系的辅助矩阵,这个很有特色。

posted @   迈吉  阅读(339)  评论(0编辑  收藏  举报
编辑推荐:
· AI与.NET技术实操系列:基于图像分类模型对图像进行分类
· go语言实现终端里的倒计时
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 25岁的心里话
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示