前言
我们之前学习了递推算法和地推套路,也说过了递推算法与动态规划暧昧不清的关系,那么,今天就来初步的学习一下动态规划。
状态转移方程
我们之前学习递推算法时所使用的递推公式,在动态规划中叫做:状态转移方程
重点
- **状态:**一个数学符号加上语义描述
- **决策:**从所有可能产生最优解的答案当中找出一个最大值或最小值。(动态规划的重点就在于决策,如果一个算法,不需要决策就能得到最优解,这类算法称为
贪心算法,至于什么是贪心算法不在我们今天的讨论范畴,我们暂且略过,之后再一起学习。) - **阶段:**当前阶段仅仅依赖上一个阶段
递推问题的求解方向
-
**我(目标值)从哪里来:**求当前状态时查看前置依赖项(pull,拉取别人的更新,就像
React和Angular数据变化侦测的方式)如之前我们学过的数字三角形的递推公式:
dp[i][j] = max(dp[i-1][j], dp[i-1][j-1]) + val[i][j]中,我们要求得dp[i][j]的话,必须先得到dp[i-1][j]和dp[i-1][j-1]的值。这就是从哪里推导出目标值的求解方向。 -
**我到哪里去:**求得当前状态时主动更新下一个状态(push,主动推送自己的更新,就像
Vue侦测数据变化的方式)还是数字三角形的题,当我们计算出来了
dp[i-1][j]的值时,我就去更新一下dp[i][j]的值,当我计算出dp[i-1][j-1]的结果时,我们再更新一下dp[i][j]的值,通过两次更新,我们也可以保证dp[i][j]的正确性。这种实现方式与上一种在思维层面和实现层面都有所不同,但也是一种可行的求解方向。
那么,什么情况下应该选择我从哪里来的求解方向求解,什么情况下选择我到哪里去的求解方向求解呢?
当我们的状态和阶段很明确,并且状态不多时,我们可以使用我从哪里来这种求解方向,这也是绝大部分动态规划问题的求解方向。而当某些情况下,我们很难说清楚我从哪里来,也就是状态和阶段太过于抽象或隐晦,或者依赖的状态实在是太多了,此时,我们可以采用我到哪里去这个求解方向。通过当前状态值主动更新下一阶段状态的值。
任何一个动态规划的程序,都可以用上面的两种方式去实现
示例
之前我们已经使用过“我从哪里来”的求解方向解决过数字三角形的问题,那么,我们现在来使用“我到哪里去”这个求解方向来求解试试
120. 三角形最小路径和
解题思路
与“我从哪里来”通过上一行数据计算下一行的数据不同,“我到哪里去”是假设当前你已经求出来dp[i][j]了,那么,此时你可以利用这个已经求出来的结果去主动更新下一行的数据。
代码演示
function minimumTotal(triangle: number[][]): number {
const n = triangle.length;
let dp: number[][] = [];
// 由于后续每次都要比较求解最小值,因此,我们初始时将dp数组的每一位都初始化为最大整型,方便后续进行大小比较
for(let i=0;i<n;i++) dp.push([]);
for(let i=0;i<n;i++) {
for(let j=0;j<=i;j++) {
dp[i][j] = Number.MAX_SAFE_INTEGER;
}
}
// 边界条件,第0行第0列的值应该就是三角形顶点的值
dp[0][0] = triangle[0][0];
// 循环每一行的数据
for(let i=0; i<n-1; i++) {
// 循环每一列的数据
for(let j=0;j<=i;j++) {
// 我们此时假设dp[i][j]已经是一个合法的值了,我们通过dp[i][j]求得dp[i+1][j]和dp[i+1][j+1]的值
dp[i+1][j] = Math.min(dp[i + 1][j], dp[i][j] + triangle[i+1][j]);
dp[i+1][j + 1] = Math.min(dp[i + 1][j + 1], dp[i][j] + triangle[i+1][j + 1]);
}
}
// 最后在dp手足最后一行找找到一个最小值就是我们要找的答案了。
return Math.min(...dp[n-1]);
};
底层结构
为何递推和动态规划会有上面说说的多种求解方向的奇特现象呢?其实,本质上,递推程序和动态规划,底层是个图结构。为什么这么说呢?我们来看一下我们动态规划的状态转移过程是怎样的:
拓扑序基础概念扫盲
拓扑序是有向图当中每个节点的一个顺序,对于每一个节点,当我们输出这个节点之前,他前面所有依赖的点,即前置节点都已经输出出来了。因此,所谓的拓扑序,就是有向图中节点的一维序列化的结果。
拓扑排序是一个图算法,由于图的含义极其广泛,因此应用非常广泛。拓扑排序就是求出一张图的其中一种拓扑序。对于一张图而言,拓扑序是不唯一的,可能有很多种,一般我们只要求出其中一种即可。拓扑排序我们一般会借助队列辅助完成排序。当某一个节点没有任何前置节点,即入度为0时,就可以把这个节点放入队列中,然后依次弹出队列就是我们要求的其中一组拓扑序了。
递推(动态规划)的求解顺序,其实就是状态依赖图的拓扑序