前言
本文的方法只能作为写动态规划过程中的一种指导,并不是所有问题都可以解决
动态规划定义
动态规划是解决搜索问题中的一种算法,该算法是利用空间换时间的思想提高算法的运行速度
如何体现出里面空间换时间的思想也有递归的思想,其实就是当我们计算dp(n)的时候是需要知道那些小规模dp(i) (i < n)的最小值,先把小规模的dp值存在dp数组中,再计算dp(n)调用dp(i)的时候就不用在重新递归那些在小规模里面递归过的数,这个把大规模的问题转成小规模的问题就是递归的感觉,所以说为什么动态规划叫做记忆化搜索,或者叫带备忘录的递归,或递归的剪枝,只是我们最后要用迭代/非递归的形式表示,递归是大规模到小规模问题拆解,动态规划是小规模到大规模迭代求解,递归是自顶而下,动态规划是自底而上,一般来说动态规划的方向和递归是相反的
剪枝的方法:解决方法就是将访问过的节点用哈希表或者dp数组进行保存,包括其节点值的信息以及所在深度的信息;在遍历的过程中,如果出现重复的节点,则直接返回。从很多题可以看出剪枝用的集合名字一般都叫做dp,从这里也可以发现记忆化搜搜和动态规划其实就是一件事,只是搜索的方向相反,也都是用dp数组存储已有的信息。
而且一般写剪枝的话要求是dfs return的不是void,而且搜索的过程是一层一层而不是往后搜索的感觉
解决方法
根据上面的分析,于是我们确定一件事,只要是能用记忆化搜索解决的问题都能用动态规划的问题解决,要解决动态规划的问题无非是解决下列几个事情:
- 确定dp数组值的含义以及下标的含义
- 确定状态转移方程
- 初始化状态
根据上文的分析,在写动态规划的过程中我们可以按照以下思路来写:
- 用递归的方法来解决问题
- 将递归的方法改成记忆化搜索
- 通过记忆化搜索的形式来确定动态规划的方法
如何根据记忆化搜索来指导动态规划的方法呢,下面开始分析
1. 确定dp数组以及下标的含义
根据分析可知,动态规划是记忆化搜索的逆过程,所以下标一般就是在递归过程中在搜索树中一直在递归的值(也就是递归函数参数中随着递归数值在改变的值),到这里你可能理解起来非常抽象,这里举一个例子:
上记忆化搜索的代码:
class Solution {
public:
unordered_map<int, int> dp;
int dfs(int n)
{
if (dp.find(n) != dp.end()