我相信读者做动态规问题时,肯定会对 dp
数组的遍历顺序有些头疼。我们拿二维 dp
数组来举例,有时候我们是正向遍历:
有时候我们反向遍历:
有时候可能会斜向遍历:
甚至更让人迷惑的是,有时候发现正向反向遍历都可以得到正确答案,比如我们在 团灭股票问题 中有的地方就正反皆可。
如果仔细观察的话可以发现其中的原因,你只要把住两点就行了:
1、遍历的过程中,所需的状态必须是已经计算出来的。
2、遍历结束后,存储结果的那个位置必须已经被计算出来。
下面来具体解释上面两个原则是什么意思。
比如编辑距离这个经典的问题,详解见后文 编辑距离详解,我们通过对 dp
数组的定义,确定了 base case 是 dp[..][0]
和 dp[0][..]
,最终答案是 dp[m][n]
;而且我们通过状态转移方程知道 dp[i][j]
需要从 dp[i-1][j]
, dp[i][j-1]
, dp[i-1][j-1]
转移而来,如下图:
那么,参考刚才说的两条原则,你该怎么遍历 dp
数组?肯定是正向遍历:
因为,这样每一步迭代的左边、上边、左上边的位置都是 base case 或者之前计算过的,而且最终结束在我们想要的答案 dp[m][n]
。
再举一例,回文子序列问题,详见后文 子序列问题模板,我们通过过对 dp
数组的定义,确定了 base case 处在中间的对角线,dp[i][j]
需要从 dp[i+1][j]
, dp[i][j-1]
, dp[i+1][j-1]
转移而来,想要求的最终答案是 dp[0][n-1]
,如下图:
这种情况根据刚才的两个原则,就可以有两种正确的遍历方式:
要么从左至右斜着遍历,要么从下向上从左到右遍历,这样才能保证每次 dp[i][j]
的左边、下边、左下边已经计算完毕,得到正确结果。
现在,你应该理解了这两个原则,主要就是看 base case 和最终结果的存储位置,保证遍历过程中使用的数据都是计算完毕的就行,有时候确实存在多种方法可以得到正确答案,可根据个人口味自行选择。