关于动态规划的讲解有很多材料,这里只是按照我自己的理解来表述动态规划。可能并不详细,也不一定完全准确。这里主要通过两个例子LIS和最小编辑距离进一步加深对于动态规划的直观理解。
1. 动态规划入门理解
动态规划方法是把问题向前分解,想要解决一个问题,需要先解决这个问题的子问题,那么要解决子问题,有需要解决子问题的子问题。通过先解决最小的子问题,再不断的解决更上一层的问题,那么就可以解决最终的问题。
欲用动态规划解决问题,首先需要对问题进行定义,利用数学语言描述问题。然后再对问题进行求解,求解的过程中是先求解小问题,再利用小问题的解去求解大问题。这是动态规划中常用的流程。
对问题的定义和求解小问题再求解大问题,其实就是定义状态和解决状态转移。下面就用两个例子(LIS和最小编辑距离)来解释如何定义问题的状态以及推导状态转移公式。
2. LIS(最长递增子序列)
LIS问题是求一个长序列的子序列,使的这个子序列是递增的,且是所有递增的子序列中最长的。比如对于序列2 5 4 9 10 6 7 8 11,最长递增子序列有两个,2 5 6 7 8 11和 2 4 6 7 8 11。最长递增子序列问题有时候是只需要知道长度即可,对于前面的序列,最长递增子序列的长度是唯一的,即=6。
当然LIS问题是可以用动态规划的思想来解决的。那么首先思考如何定义问题的状态。最容易想到的定义如下:
: 表示序列前k项的最长子序列
对于这个定义,很明显,子问题就是 , 等,分别表示前 和前 项的最长子序列。对问题进行定义后,是否合适呢?能否通过该定义推导出状态转移呢?如果对于每一项都求最长的子序列,并且状态转移使用 项和其它的最长子序列相比较的方式,选择是否把 项加入。
对于序列2 5 4 9 10 6 7 8 11
第一项:2
第二项:2 5
第三项:2 5
第四项:2 5 9
第五项:2 5 9 10
第六项:2 5 9 10
第七项:2 5 9 10
….
从这里就可以看出来,该种状态的定义和状态转移的定义是有问题的。6 7 8 这几项不能发挥作用了。
- 因此这里选择另外一种定义:
: 表示以第k项结尾的最长子序列
- 那么求出所有以1,2,3,4,5…,N项结尾的最长子序列后,选择最长的作为输出即可。那么对于这种状态的定义,状态的转移是什么样子的呢?
也就是,求解序列[ , , , …, ]的最长递增子序列,可以通过[ , , , … ]序列、[ , , , … ]、[ , ]和[ ]的最长递增子序列得到。要求长度为k的序列的[ , , , …, ]最长递增子序列,需要先求出序列[ , , , …, ]中以各元素( , , )作为最大元素的最长递增序列,然后把所有这些递增序列与 比较,如果序列的末尾元素比 要小,则将元素 加入这个递增子序列,如果序列末尾的元素比 大,则取 ,那么现在所有的序列都是以 结尾的,取出序列长度最长的作为以第k项结尾的最长递增子序列。(这里可能会出现两个序列长度一样的情况,那么可以随机选一个,或者是选择第一个)。
- 对于前面的序列2 5 4 9 10 6 7 8 11,求解顺序如下(存在两个一样长的序列时,选择第一个):
第一项:2
第二项:2 5
第三项:2 4
第四项:2 5 9
第五项: 2 4 9 10
第六项:2 5 6
第七项:2 5 6 7
第八想:2 5 6 7 8
第九项:2 5 6 7 8 11
另外,如果是求子序列,那么是一边计算,也是一边需要保存各项的子序列的。如果是只求子序列的长度,那么只需要保存长度即可。
采用这种方式求解最长递增子序列,时间复杂度为 ,还有更快的,时间复杂度为 的算法,这里就不再解释了。
3. 最小编辑距离
- 最小编辑距离的问题,比较普遍,在平常的编码过程中经常可能会遇到这个问题。最小编辑距离是指把字符串A转换成字符串B所需要的最少操作数,这里的操作包括删除字、增加字、替换字三种操作。
比如小明让小红告诉小刚:“今天天气真好啊”,而小红却告诉小刚:“今天正好呀哈”。 那么就可以用最小编辑距离表示这两句话的区别。这里,增加“天”字,增加“气”字,修改“正”为“真”,修改“呀”为”啊”,删除“哈”字。那么最小编辑距离为5。
要用动态规划的思想解决该问题,同样需要考虑如何定义状态和建立状态转移公式。
首先对问题进行定义:
: 表示从句子A的前i个字转移到句子B的前j个字的最小编辑距离
状态转移公式如下(假设句子A有N个字符,而句子B有M个字符):
其中
更详细的最小编辑距离算法的说明,可以参考博客https://blog.csdn.net/qq_34552886/article/details/72556242