1068 Find More Coins
1.回溯法
将数据从小到大进行排序,然后依次考虑有各个物品的情况,逻辑如图,是一个深度遍历该树的过程。(左分支为选取某个物品,右分支为不选取)
这种方法效率低,尤其是在没有答案的时候,要遍历完整棵树才能得到结果,最差时间复杂度为O(2^n)。
2.动态规划
本题也可以理解为01背包问题(背包容量就是要求的数总和,物品个数就是数的个数,每件物品价值等于物品重量,所以背包价值等于背包重量),用动态规划解决。而在使用动态规划解题,有如下几个注意的点:
1)f [m][n]的求解
在01背包问题,设背包容量为9,物品总数为8,各物品总量为 w[i] 。约定一个数组f[8+1][9+1],数组f[m][n]存放对于前m个物品,背包容量为n时物品总价值的最大值。f[m][n] = max { f[m-1][n],f[m-1][n-w[i]] },我们目标是求 f[8][9] (下标从1开始),根据递推公式很容易就有了自顶向下的求法,如下图。
这种方法没有缩小时间复杂度还是O(2^n),但是如果换个思路,自底向上,如图。
(1,1) | (1,2) | (1,3) | … | (1,9) |
---|---|---|---|---|
(2,1) | (2,2) | (2,3) | … | (2,9) |
(3,1) | (3,2) | (3,3) | … | (3,9) |
… | … | … | … | … |
… | … | … | … | … |
(8,1) | (8,2) | (8,3) | … | (8,9) |
从第一行出发从左到右,从下至下求得整个数组,或者从列出发也是可以的,从而实现将时间复杂度缩小到O(m*n)。
2)进一步得缩小空间复杂度
前面我们使用二维数组 f[m][n] 来保存信息,实际上还可以压缩成一维数组。
我们观察可以发现,求取数组中某个位置的值,只和前一层数据有关(不是这一层,不是前两层,前三层等等,仅仅前一层)。所以我们依旧是从上到下求解这个数组,但是每层从后往前求。
假如,设数组 g[9+1],求解原来的 f[2][9],放置于 g[9]。f[2][9]仅需要 f[1][1-8]
,而g[1-8]刚好存放的就是这些值。这就是为什么每层倒序求的原因,能保证所求位置前面的数据都是上层数据,而求取的时候也只使用到该位置以前的数据。
2)是否有解,以及求取最小解
求取出了g[n],如果 g[n]==n 那么就是有解,否则无解。
而关于最小解,首先,我们需要将所有物品(数字)倒序排列。其次,我们需要设置一个 初值均为 false 的choose[m][n] 数组。如果遍历到物品 i,重量 j 时,g[j]更新了,那么 choose[i][j]=true,即可以理解为对于重量 j的所有解,物品 i 可以是其中一个解的一员。 我们可以利用choose[m][n]把解拆出来,拆分从最后一个物品开拆,那么我们前面倒序排列的目的就很明确了,就是为了优先把重量小的物品拆出来。
最后,我们按如下规则遍历choose数组,就能得到最小解。
int v = n, index = m;
//v初值为n是因为求的是总和为n的解,index初值为m是因为优先考虑编号
while(v > 0) {
if(choose[index][v] == true) {//如果是解的成员,拆出来
arr.push_back(w[index]);
v -= w[index];//改变重量
}
index--;//改变可拆物品范围
}