题目描述
解题思路
硬币组合问题是一个经典的动态规划问题,这里笔者将其比作完全背包问题,因为硬币是无限使用的,而且容量是固定的(总数),所以就相当于是一个完全背包问题,顺带一提,像这么看,楼梯攀登问题也是一个完全背包问题。 法
那么就按照动态的思路走喽。
dp数组的含义
dp[i]表示i容量的背包最少用dp[i]个硬币,因为我们累加的是硬币的个数,所以拿硬币来做文章是理所当然的事情。
递推公式
显然对于do[i]来说,它需要知道的是dp[i]-1个硬币时候能够使用的最小硬币数,所以,dp[i] = min(dp[i - coins] + 1, dp[i]),通过遍历硬币的面值,不断的查找哪个最小,就能由局部最优到达全局最优,这就是动态规划思想的精髓。
遍历顺序
显然,这里有硬币面值和背包容量两个维度,所以有兴趣的同学可以去查看一下用二维是怎么解决这个问题的,其实二维和一维是一样的,只是一维使用了状态压缩,更为精简。
所以我们需要两层循环,在外层先遍历背包容量,里面遍历硬币面值,因为数值是从前面的状态才能得到后面的状态,所以是正向的遍历,在这里细心的读者就会发现和一维的零一背包的遍历顺序不同了,那是因为零一背包的物品智能使用一次,所以它再次遍历容量时候必然会因为前面的数值被改变而影响到后面的结果,但是完全背包就不需要考虑这个,因为物品无线,所以我只要从底层出发就没问题。
代码呈现
for(int i = 0; i < dp.length; i++){
for(int j = 0; j < array.length; j++){
if( i >= array[j])
dp[i] = Math.min(dp[i - array[j]] , dp[i])
}
}
难点
注意到本题需要的不是最少需要多少硬币,而是硬币的数组,因此我们就需要来一个path来记录着这个添加硬币的路径,使得我们的添加有一个可以寻找的方向,而难点就在这里,每一次的维护都需要我们先去删除掉之前的之前的路径,再对当前路径来进行添加。
dp[i].setNum(dp[i - coin].getNum() + 1);
dp[i].path.clear(); // 清空当前路径
dp[i].path.addAll(dp[i - coin].getPath()); // 添加之前的最佳路径
dp[i].path.add(coin); // 添加当前硬币
总结
本题主要就是考察大家对题意的理解,还有对完全背包的掌握程度,看能否将其转化到背包问题,也考察了大家对背包遍历方向的理解,最后还在路径这里对大家挖了一个坑,总体是一个不错的题目。