常用算法之动态规划学习笔记
动态规划问题的一般形式是求最值,求解动态规划的核心问题是穷举,穷举所有的答案找最值。
因为这类问题存在 重叠子问题 的情况,所以需要 备忘录(自顶向下)或者DP(Dynamic Programming) table(自底向上) 来优化穷举过程。一定存在最优子结构(子问题的解答互相之间不影响),才能通过子问题的最值得到原问题最值。
因此有动态规划三要素:重叠子问题、最优子结构、状态转移方程。
如斐波那契数列可以用动态规划的思想进行优化,下面以经典样例 凑零钱问题 为例,阐释动态规划问题的解决方式。
问题描述:
给你k种⾯值的硬币,⾯值分别为c1, c2 … ck ,每种硬币的数量⽆限,再给⼀个总⾦额amount ,问你最少需要⼏枚硬币凑出这个⾦额,如果不可能凑出,算法返回 -1 。
函数声明如下:
int coinChange(int [] coins, int amount)
状态转移方程:
重点是列出状态转移方程,主要思路如下:
- 先确定状态,原问题和子问题中变化的变量,硬币数量无限,所以唯一的状态是目标金额amount
- 确定dp 函数的定义:当前的目标金额为n,至少需要 dp(n) 个硬币凑整
- 确定选择并择优,列出n取不同情况时的dp(n)取值,同时明确base case,也就是n = 0或n < 0特殊情况时的返回值
得到状态转移方程:
对应的Java代码:
public int coinChange(int[] coins, int amount) {
int[] dp = new int[amount + 1];
dp[0] = 0;
for (int i = 1;i <= amount;i++) {
boolean hasChange = false;
int min = amount;
for (int coin : coins) {
int index = i - coin;
if (index >= 0 && dp[index] >= 0) {
int value = dp[index] + 1;
if (min > value) {
min = value;
}
hasChange = true;
}
}
if (!hasChange) {
dp[i] = -1;
} else {
dp[i] = min;
}
}
return dp[amount];
}