首先,动态规划问题的一般形式就是求最值。动态规划其实是运筹学的一种最优化方法,只不过在计算机问题上应用比较多,比如说让你求最长递增子序列呀,最小编辑距离呀等等。
既然是要求最值,核心问题是什么呢?求解动态规划的核心问题是穷举。因为要求最值,肯定要把所有可行的答案穷举出来,然后在其中找最值呗。
动态规划这么简单,就是穷举就完事了?我看到的动态规划问题都很难啊!
首先,虽然动态规划的核心思想就是穷举求最值,但是问题可以千变万化,穷举所有可行解其实并不是一件容易的事,需要你熟练掌握递归思维,只有列出正确的「状态转移方程」,才能正确地穷举。
而且,你需要判断算法问题是否具备「最优子结构」,是否能够通过子问题的最值得到原问题的最值。
另外,动态规划问题存在「重叠子问题」,如果暴力穷举的话效率会很低,所以需要你使用「备忘录」或者「DP table」来优化穷举过程,避免不必要的计算。
以上提到的重叠子问题、最优子结构、状态转移方程就是动态规划三要素。具体什么意思等会会举例详解,但是在实际的算法问题中,写出状态转移方程是最困难的,这也就是为什么很多朋友觉得动态规划问题困难的原因,我来提供我总结的一个思维框架,辅助你思考状态转移方程:
我一直有个疑问是最优子结构怎么确定是动态规划的核心,有最优子结构就能用动态规划,如何判断是否能用动态?
我们讨论动态规划时,最优子结构是一个关键特性。它指的是一个问题的最优解包含其子问题的最优解。也就是说,我们可以通过子问题的最优解来构造原问题的最优解。
在硬币找零问题中,最优子结构体现在:要凑成总金额`total`的最少硬币数,我们可以先凑成`total - v`(其中`v`是某个硬币面值)的最少硬币数,然后加上一枚面值为`v`的硬币。这样,原问题的最优解就由子问题(凑`total-v`)的最优解加上1得到。
但要注意:我们必须确保子问题`total-v`的解的确是最优的,并且我们考虑了所有可能的硬币面值`v`,从中选择最小的硬币数量。
举例说明:
假设硬币面值[1, 2, 5],总金额11。
最优解是:5+5+1(3枚)或者5+2+2+2(4枚)等,但最少是3枚。
如何用最优子结构解释?
我们考虑最后一步,假设最后一步使用的硬币是1,则子问题是凑11-1=10,那么我们需要知道凑10的最少硬币数(已知是2枚:5+5)然后加上1枚,共3枚。
如果最后一步是2,则子问题凑9,凑9的最少硬币数是多少?9=5+2+2(3枚),那么总数为4枚。
如果最后一步是5,则子问题凑6,凑6的最少硬币数是2枚(2+2+2或5+1,但最少2枚?实际上6=5+1需要2枚,6=2+2+2需要3枚,所以最少2枚),那么总数为3枚。
因此,我们取这三种情况的最小值:min(3, 4, 3)=3。
这里,我们依赖于子问题(10、9、6)的最优解。也就是说,我们假设已经知道凑10、9、6的最少硬币数,然后通过比较得到11的最少硬币数。
这就是最优子结构:问题的最优解(11的最少硬币数)由子问题的最优解(10、9、6的最少硬币数)组合得到。
然而,需要注意的是,在动态规划中,我们通常自底向上计算,先计算0,1,2,...,直到11。这样,在计算11时,子问题10、9、6都已经计算过了。
为什么需要最优子结构?
因为动态规划依赖于子问题的解来构建原问题的解。如果子问题的最优解不能构成原问题的最优解,那么动态规划就无法应用。