1. 动态规划可以解决哪些问题?
(1)计数
-有多少种方式走到右下角
-有多少种方法选出k个数使得和是sum
(2)最值
-从左上角走到右下角路径的最大数字和
-最长上升子序列长度
(3)存在性
-取石子游戏,先手是否必胜
-能不能选出k个数使得和是sum
2. 动态规划步骤
2.1 确定状态 state
状态在动态规划中属于定海神针。简单的说,解动态规划的时候需要开一个数组,数组的每个元素f[i]或者f[i][j]代表什么,类似于数学题中,X, Y, Z代表什么。
确定状态需要两个意识:
1)最后一步
2)子问题
下面以一个例子进行讲解:
有足够多的三种面值的硬币:2元,5元和7元,要支付总额为27元的商品,要求支付的硬币数量最少。
2.1.1 最后一步
虽然我们不知道最优策略是什么,但最优策略肯定是K枚硬币a1, a2 ... ak面值加起来是27.
所以一定有一枚最后的硬币:ak,除掉这枚硬币,前面的硬币面值加起来是27-ak。
关键点1:
我们不关心前面的k-1枚硬币是怎么拼出27-ak的(可能有1种拼法,也可能有100种拼法),而且我们甚至还不知道ak和k,但我们确定前面的硬币拼出了27-ak。
关键点2:
因为是最优策略,所以拼出27-ak的硬币数一定最少,否则这就不是最优策略了。
2.1.2 子问题
原问题是如何用最少枚硬币拼出27,现在我们将问题转为了:如何用最少枚硬币可以拼出27-ak,转成了一个比原问题规模更小的一个子问题。
为了简化定义,我们设状态f(X)=最少用多少枚硬币拼出X。
但我们还不知道最后那枚硬币ak是多少,但ak只可能是2,5或者7,所以:
1)如果ak是2,则f(27) = f(27-2) + 1(加上最后的这枚2元的硬币);
2)如果ak是5,则f(27) = f(27-5) + 1(加上最后的这枚5元的硬币);
3)如果ak是7,则f(27) = f(27-7) + 1(加上最后的这枚7元的硬币);
除此之外,没有其他可能性了,所以:
f(27) = min(f(27-2) + 1, f(27-5) + 1, f(27-7) + 1)
2.1.3 递归解法
递归存在的问题:
做了很多重复计算,效率低下。
我们可以将计算的结果保存下来,并改变计算顺序。
2.2 转移方程
设状态f(X)=最少用多少枚硬币拼出X
那么对于任意X,有以下等式:
f(X) = min(f(X-2) + 1, f(X-5) + 1, f(X-7) + 1)
转移方程其实就是将一个大的问题拆分成若干个规模更小的子问题。
2.3 初始条件和边界条件
要解决的是算法的最小拆分单元(再小就不能拆了),以及算法何时结束(不能无止境的计算下去),还要防止程序出现异常(例如数组不能越界),
初始条件f(0) = 0; 状态不能从转移方程求得,但是它是有值的;
2.4 计算顺序
2.4.1 自顶向下
从最大的问题触发开始解,拆分成每个对应的子任务,直到子任务是初始条件。
2.4.2 自底向上
由初始条件f(0) = 0开始,逐步计算f(1), f(2)...f(27)得到结果