动态规划(Dynamic Programming)
- 求解最优化问题的一种常用策略
 - 通常的使用套路(一步一步优化)
- 暴力递归(自顶向下,出现了重叠子问题)
 - 记忆化搜索(自顶向下)
 - 递推(自底向上)
 
 - 动态规划中的“动态”可以理解为是“会变化的状态”
- 定义状态(状态是原问题、子问题的解)
 - 设置初始状态(边界)
 - 确定状态转移方程
 
 
假设有25分、20分、5分、1分的硬币,现在要找给客户41分的零钱,如何办到硬币个数最少
如果使用贪心策略,我们将优先选择最大的硬币,即25->5->5->5->1,一共需要找5个硬币,但很明显,这并不是最优解.
我们只需要大致想一下,最优解应该为20->20->1,一共三个硬币.
接下来我们使用动态规划的思路来求解这个问题.
- 假设dp(n)是凑到n分需要的最少硬币个数
- 如果我们第一次选择25分的硬币,那么dp(n)=dp(n-25)+1.
 - 如果我们第一次选择20分的硬币,那么dp(n)=dp(n-20)+1.
 - 如果我们第一次选择5分的硬币,那么dp(n)=dp(n-5)+1.
 - 如果我们第一次选择1分的硬币,那么dp(n)=dp(n-1)+1.
 
 - 即dp(n)=min{dp(n-25),dp(n-20),dp(n-5),dp(n-1)}+1
 
1  | public class Main{  | 
将上述想法转换成代码,我们可以得到正确答案,最少为3个硬币。
通过上面的过程,明显可以看得出来是一个递归过程,就像我们最开始求斐波那契数一样。
1  | static int fib(int n){  | 
这就是我们刚才讲的第一种境界:“暴力递归”
不出意外地,我们会进行很多重复的计算,即出现了重叠子问题.
接下来我们采用记忆化搜索,对其进行优化
1  | public class Main{  | 
即我们利用dp数组存储了可能算过的值,叫做“记忆化搜索”,对上述暴力递归进行了优化。
当然,这也不是动态规划!
接下来我们利用动态规划来解决这个问题
1  | public class Main{  | 
以上就是动态规划的思想了, 也就是递推法!
如果想知道每次取的是哪几个硬币,我们也可以稍微修改一下上述代码
1  | public class Main{  | 
通过上述方案,我们就可以得到取出的是哪一些硬币。
LeetCode 322、零钱兑换
https://leetcode-cn.com/problems/coin-change/
有了上面的思路,我们要实现一般化的过程也很简单。
1  | class Solution {  |