动态规划(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 { |