题目:
假设有 8元,3 元,1 元,5元的硬币若干(无限),现在需要凑出3 元,问有多少种情况可以凑出硬币
动态规划的解题四步骤
步骤参考了csdn上面的讲解,很细致,所以借用这个思路总结
动态规划的的四个解题步骤是:
-
定义子问题
-
写出子问题的递推关系
-
确定 DP 数组的计算顺序
-
空间优化(可选)
第一步,定义子问题。
设硬币面值的集合为C{c2,c2,c3,...,cm},子问题是f(K)
那么f(k)子问题 可以定义为:
在选择集合C中的数据,可以凑出金额k的方案的个数是多少,来进行分析规划。
第二步,写出子问题的递推关系。
考虑第一个硬币放哪个,如果放1的话,剩下的方案k-1的方案个数就是f(k-1)也就是,凑出金额k-1的方案的个数是多少
我们就由此可以得到递推关系:
子问题的 base case:
表示:凑出金额0的方案数是1(什么情况下需要凑出金额0呢?也就是上一步已经完整的得到了我们凑出的数)
第三步,确定 DP 数组的计算顺序。
确定 DP 数组计算顺序的重点是看子问题的依赖关系。
我们可以判定,8>需要凑出的元素3,所以排除可能
DP 数组中的每个元素只依赖其左边的元素。在本题中,f(k)依赖于f(k-1),f(k-3)和f(k-5)三种情况
怎么理解呢:
比如我们定义现在的情况是f(k),那么他的前一步,就可能是f(k-1),f(k-3)和f(k-5)三种情况我们笼统的定义为f(k-i)
假设前一步是f(k-1)的话 在前一步,就会是f(k-1-1);f(k-1-3)和f(k-1-5)三种情况
DP数组图:
有点类似于递归的思想,那么什么时候跳出这种类递归呢?就是在f(0)的时候,凑出金额为0了跳出递归返回值了。
代码如下:
public int combinationSum4(int n, int[] coins) {
// 创建dp数组,默认初始化为0,大小为需要规划的值+1
int[] dpResult = new int[n + 1];
// 定义 base case,也就是f(0)的情况=1
dpResult[0] = 1;
//遍历dp数组,从第二位到最后
for (int k = 1; k <= n; k++) {
//遍历coins数组
for (int i=0; i<coins.length; i++) {
//判定k>=n的情况,也就是k在右边,依赖于K左边的[k-i]的元素
if (k >= i) {
dpResult[k] += dpResult[k-i];
}
}
}
return dpResult[n];