首页 > 其他分享 >动态规划的一种常见技巧

动态规划的一种常见技巧

时间:2024-07-12 13:25:40浏览次数:8  
标签:技巧 常见 问题 索引 lis 序列 长度 动态 最长

动态规划是运筹学的一个分支,是求解决策过程最优化的过程。
动态规划并不是一种算法,而是一种思想,或者说策略
动态规划的思想就是将大问题分解成一个一个的小问题,聚焦到每个小问题并逐个击破,小问题解决了就没有大问题了
我们以一个关于最长递增子序列问题为例,设想你有一个包含N个元素的序列,你的任务是找到最长递增子序列的长度
比如说
列表[3, 1,8,2,5],最长子序列长度为3,子序列为[1, 2, 5]
列表[5, 2, ,8, 6, 3, 6, 9, 5]最长子序列长度为4, 子序列为[2, 3, 6,9]

需要说明的一个重点是,为了简化,我们目前将专注于寻找序列的长度,而不是序列本身

所以

解决动态规划问题的第一步是寻找一种可视化示例的方式,可视化是发现问题中与解决方案相关的连接和基础模式的有效方法,在解决这个具体问题时,我们显然会遇到一些关于有效序列的约束,因此,找到一种展示有效序列的方式是非常有益的。
在动态规划中,一个常见的模型是有向无环图,设想序列中的每个元素都是图中的一个节点,如果右侧的节点具有更大的值,我们就在两个节点之间建立一个有向边,下面是这个特定输入序列的有向无环图表示,这种表示法的一个优势是,在图中递增子序列仅仅是另一条路径。
在这里插入图片描述
实际上,更深入的看,最长递增子序列的长度对应于此有向无环图中最长路径的长度+1,因此,我们在技术上是在计算节点。
对这个特定问题的解决方案可能会变得更加明确,而有时,这种视角的转换正是使挑战问题变得可解的关键。

解决动态规划问题的第二步是找到合适的子问题,子问题实质上是整个问题的简化版,确定子问题可能有一些难度,但让我们关注我们已知的关于这个问题的信息。

我们知道,最长递增子序列会是初始序列的一个特定子集,确定这个子集的一种方法是通过它的起始和终点。
每个递增子序列无论长度如何,都有一个起始点和终点在原始序列中。因此,我们可以通过修改这些变量中的一个,来定义一个子问题
在这里插入图片描述
事实上,解决这个问题可以通过任意方法,但我个人觉得关注子序列的终点更为直观

我们为序列定一个子问题,命名为索引k处的lis,这意味着最长递增子序列结束于索引k。例如,结束在3的lis会是从1开始,再到2的序列,长度为2,也就是
lis[3] = 2
记住,当我们讲述这个特定问题中的lis时,我们专门指的是序列的长度。既然我们已经定义了一个可能的子问题,第三步就是要探寻子问题之间的联系。在此阶段,向自己提出一些问题通常会非常有助于思考。例如,假设你想解决找到以索引4结尾的最长上升子序列的问题。为了解这个问题,需要解决哪些子问题呢?

在这里插入图片描述
OK,这样图像化的可视化很有用,因为它清晰地展示了我们需要什么样的子问题。经过索引4的一条路径必须从索引0经过,所以我们需要知道以索引0结尾的最长上升子序列的长度,这刚好是1.
另一条路径从索引1经过,我们也需要知道这个子问题的答案,其长度也是1。最终可能以索引4结尾的路径从索引3经过,而以索引3结尾的最长上升子序列的长度是2
lis[0] = 1 lis[1] = 1 lis[3] = 2

因此,最长上升子序列的长度将是1加上所有相关子问题的最大值,即3,直观上来说,这是有道理的对吗?
如果我要找出以索引4结尾的最长上升子序列,我只需要在所有最终能达到索引4的子序列的最长上升子序列之上加1,这确实很有道理
lis[4] = 1 + max{lis[0], lis[1], lis[3] } = 3

所以,一旦我们发现了子问题之间的联系,我们就需要概述这些联系,也就是第四步。
我们再来举一个例子,看看是否能找到一个类似的流程,来解决以索引5结尾的最长上升子序列
在这里插入图片描述

这里的核心思想是,我们只考虑以索引k结尾的子问题,当且仅当k小于5,且索引k处的值小于索引5处的值。
为了见证这一关系如何运作,我们从k等于0开始,由于索引0处的位置小于6,我们需要知道这一子问题的解答
我们将继续探讨所有可能的k值,k小于5,并涉及符合题目所述限制的子问题,实际上,所以5并无特殊性
lis[5] = 1 + max{lis[k] | k < 5, A[k] < A[5]}

此逻辑适用于任何n,因此,找到以索引n结尾的最长上升子序列的子问题的一般解即为 1加上所有符合k小于n且索引k处的值小于索引n处的值的k的最大值
lis[n] = 1 + max{lis[k] | k < n, A[k] < A[n]}

现在我们准备开始实现,这是最后一步,也就是第五步,实现一个动态规划方案,其实就是按照适当的顺序解决子问题,最关键的是,在解决某个特定子问题之前,所有相关的子问题都应已解决。对于这个问题,解决问题的顺序实际上相当直观。我们必须从左至右解决子问题。现在,让我们实现一个函数

def lis(A):
    L = [1] * len(A)
    for i in range(1, len(A)):
        subproblems = [L[k] for k in range(i) if A[k] < A[i]]
        L[i] = 1 + max(subproblems, default=0)
    return max(L, default=0)

我们将用一个列表来记录长度,我们可以把所有长度初始化为1,因为每个上升子序列最少包含一个元素
然后,我们会针对输入列表的从1开始的每个索引的长度,首先确定必要的子问题,接着依据我们设定的概述来更新长度
最终,我们会返回刚刚更新的长度列表中的最大长度。实现这个功能有多种方法,因此,不要过于纠结于细节

这里,我们需要记住的一点是,我们应使用一种思维过程,以正确的顺序识别并解决子问题。我们还需要解决最后一个关键问题。直至目前,我们所做的一切,都是为了寻找最长上升子序列的长度。但我们如何真正找到实际的基础序列呢?实际上,这里的关键思路非常简单。我们需要做的,仅仅是为特定的子问题追踪前一个索引。更确切的说,如果我用索引j处的最长上升子序列的值来解决以索引i结尾的最长上升子序列的子问题,那么我可以确定索引i的前一个索引是索引j
在这里插入图片描述

我们可以观察一个具体的例子,以便更加清晰的理解。在这个序列中,索引0的前一个索引可以标为-1,因为没有序列值在索引0之前。索引1的前一个索引也一样。
prev[0] = -1 prev[1] = -1
对于索引2,前一个索引可以是索引0或索引1,选择哪一个都没关系,因为他们具有相同的长度值。
prev[2] = 0
而对于索引3,前一个索引只有一个选择,即索引1
prev[3] = 1
最终,索引4只有一种选择,因为计算索引4处的长度的子问题是以索引3为终点的最长上升子序列,因此前一个索引也就是3
prev[4] = 3

这种追踪前一个子问题的模式是解决动态规划问题的一种常见技巧

标签:技巧,常见,问题,索引,lis,序列,长度,动态,最长
From: https://blog.csdn.net/qq_41780297/article/details/139951211

相关文章

  • 网络流-最小割常见模型
    最多限制相交路径问题已知一些路径,每一个节点可以属于多个路径,但是属于路径的数量不得超过一个给定的上限。解决方法将\(1\)个节点拆为\(2\)个,接着进行连边,其中容量代表可以经过的路径。最大权闭合图定义如果一个点集满足其中任意元素可以到达的所有元素都在集合中,那么......
  • 网络流-最大流常见模型
    二分图最大匹配问题给定一个二分图\(G\),即分左右两部分,各部分之间的点没有边连接,要求选出一些边,使得这些边没有公共顶点,且边的数量最大。解决方法保留原有部分的连边,将所有连边设为从左向右的容量为\(1\)的有向边。将\(S\)用容量为\(1\)的边连接所有左边的节点,把所有右......
  • 网络流-费用流常见模型
    二分图最大权匹配问题选择一些边,如果满足任意两条边都没有公共端点,那么这些边被称为二分图的一组匹配。二分图最大权匹配就是寻找所有的二分图的匹配中权最大的,注意权最大是第一关键字,而是否是匹配最多的无关紧要。求解方法首先,在图中新增源点\(S\)与汇点\(T\)。从\(S\)......
  • 一些 C++ 的卡常技巧
    是的,这篇文章的主要内容非常好懂,相信各位同学也十分感兴趣毕竟哪位OIer不想自己的代码跑得飞快呢?那么我们就进入正题吧!First众所周知,一份代码里面必然会有很多循环打表的话当我没说,而循环自然是十分占时间的。所以我们要做的就十分清楚了:加速循环!1.把int改成registerin......
  • 在生产环境中部署Elasticsearch:最佳实践和故障排除技巧——安装篇(一)
    #在生产环境中部署Elasticsearch:最佳实践和故障排除技巧——安装篇(一)前言关键字:机器学习人工智能AIchatGPT学习实现使用搭建深度python事件远程dockermysql安全技术部署技术自动化代码文章目录-------------......
  • 在生产环境中部署Elasticsearch:最佳实践和故障排除技巧——聚合与搜索(三)
    #在生产环境中部署Elasticsearch:最佳实践和故障排除技巧——聚合与搜索(三)前言文章目录前言-聚合和分析-执行聚合操作-1.使用JavaAPI执行聚合操作-2.使用CURL命令执行聚合操作-1.使用JavaAPI执行度量操作-2.使用CURL命令执行度量操作-使用缓存-调......
  • 经典再现,回顾常见排序算法之冒泡排序,附Java源码及优化改进实现
    回顾一下排序算法,老酒装新瓶,给自己的技能点做个回放。排序(Sorting)是计算机程序设计中的一种重要操作,它的功能是将一个数据元素(或记录)的任意序列,重新排列成一个有序的序列,也可以理解为高矮个站队。衡量排序算法的两个指标,时间复杂度和稳定性。举个例子,如果我们的数据......
  • Journalctl命令常见用法
    1、查询指定时间范围内的日志journalctl--since"2023-01-0100:00:00"--until"2023-01-0223:59:59"journalctl-usshd--since"2023-05-01"--until"2023-5-31"-n202、查询指定系统单元服务的日志journalctl-unginx--sinceyesterdayjournalc......
  • 在Linux中,列出几种常见打包工具并写相应解压缩参数。
    在Linux中,有多种常见的打包工具,它们各自具有不同的特点和用法。以下是几种常见的打包工具及其相应的解压缩参数:1.tar简介:tar(tapearchive)是一种广泛使用的Linux打包工具,它主要用于将多个文件和目录打包成单个文件,但不进行压缩。通过与其他压缩工具结合使用,可以实现打包和压缩......
  • nginx常见命令
    启动nginx`./nginx`关闭nginx`nginx-sstop``nginx-squit`查看相应进程`psaux|grepnginx`关闭相应进程`psaux|grepnginx`找出进程id(pid)后`kill-sQUIT`或者直接使用killall命令终止所有nginx相关进程:`killallnginx`关......