参考博文:
1、经典中的经典算法:动态规划(详细解释,从入门到实践,逐步讲解) 作者:BS有前途
2、告别动态规划,连刷40道动规算法题,我总结了动规的套路(微信公众号:帅地玩编程)
3、LeetCode:120 三角形最小路径和 动态规划解法 O(n)空间 作者:AkagiSenpai
一、动态规划的定义:
维基百科:
动态规划(Dynamic Programming, DP)在查找有很多重叠子问题的情况的最优解时有效。它将问题重新组合成子问题。为了避免多次解决这些子问题,它们的结果都逐渐被计算并被保存,从简单的问题直到整个问题都被解决。因此,动态规划保存递归时的结果,因而不会在解决同样的问题时花费时间。
动态规划只能应用于有最优子结构的问题。最优子结构的意思是局部最优解能决定全局最优解(对有些问题这个要求并不能完全满足,故有时需要引入一定的近似)。
动态规划算法是通过拆分问题,定义问题状态和状态之间的关系,使得问题能够以递推的方式去解决。
动态规划算法的基本思想与分治法类似,也是将待求解的问题分解为若干个子问题(阶段),按顺序求解子阶段,前一子问题的解,为后一子问题的求解提供了有用的信息。在求解任一子问题时,列出各种可能的局部解,通过决策保留那些有可能达到最优的局部解,丢弃其他局部解。依次解决各子问题,最后一个子问题就是初始问题的解。
二、基本思想与解题策略:
动态规划,无非就是利用历史记录,来避免我们的重复计算。而这些历史记录,我们得需要一些变量来保存,一般是用一维数组或者二维数组来保存。
1、首先是拆分问题,就是根据问题的可能性,把问题划分成一步一步。这样就可以通过递推或者递归来实现。
关键就是这个步骤,动态规划有一类问题就是从后往前推。有时候我们很容易知道:如果只有一种情况时,最佳的选择应该怎么做。然后根据这个最佳选择往前一步推导,得到前一步的最佳选择。
2、找出数组元素之间的关系式,我觉得动态规划,还是有一点类似于我们高中学习时的归纳法的,当我们要计算 dp[n] 时,是可以利用 dp[n-1],dp[n-2],…,dp[1],来推出 dp[n] 的,也就是可以利用历史数据来推出新的元素值,所以我们要找出数组元素之间的关系式,例如 dp[n] = dp[n-1] + dp[n-2],这个就是他们的关系式了。而这一步,也是最难的一步,后面我会讲几种类型的题来说。
3、为减少重复计算,对每一个子问题只解一次(重点),将其不同阶段的不同状态保存在一个二维数组中。比如我们找到最优解,我们一个先将最优解保存下来,为了往前推导时能够使用前一步的最优解,在这个过程中难免有一些相比于最优解差的解,此时我们应该放弃,只保存最优解,这样我们每一次都把最优解保存了下来,大大降低了时间复杂度。
三、详细代码分析、推导、改进
话不多说,咱们来看一道经典例题(Leetcode 120.Triangle):
Given a triangle, find the minimum path sum from top to bottom. Each step you may move to adjacent numbers on the row below.
For example, given the following triangle
[
[2],
[3,4],
[6,5,7],
[4,1,8,3]
]
The minimum path sum from top to bottom is 11 (i.e., 2 + 3 + 5 + 1 = 11).
Note:
Bonus point if you are able to do this using only O(n) extra space, where n is the total number of rows in the triangle.
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/triangle
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
这道题是找最小和,只能从左下角和右下角加上去。
但是,下面因为借鉴了一篇博文,为了少写一下,我就搬运他的部分内容过来了。他是最大值,性质是一样的,并不影响对动态规划的理解。
下面我们开始来分析了,
代码 Vision-1:
注:这个版本的代码是最初版本的代码,之所以放上来,是为了方便理解动态规划。如果放到Leetcode,是超时的。
可以看出每走第n行第m列时有两种后续:向下(i+1,j)或者向右下(i+1,j+1)
最后一行可以当做边界条件,所以我们自然而然想到递归求解。