动态规划+回溯 高效解决凑硬币问题
这是我在刷leetcode遇到的一道典型动态规划题,我们先看下问题:
给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。
示例 1:
输入: coins = [1, 2, 5], amount = 11
输出: 3
解释: 11 = 5 + 5 + 1
示例 2:
输入: coins = [2], amount = 3
输出: -1
一般来说,只要看到求最值,一般都是动态规划的问题(不过 动态规划 != 求最值问题 )
具体是不是,这个做多了就知道了
我们设 i 为 需要凑的金额
dp[i] 为 凑成 i 金额所需的最少的硬币个数
设c 为 某个面额的硬币
那么dp[i] = min(dp[i - c] + 1, dp[i])
下面是动态规划实现的c++代码:
int coinChange(vector<int>& coins, int amount) {
if (amount == 0) {
return 0;
}
vector<int> dp(amount + 1, amount + 1);
for (auto c : coins) {
if(amount >= c)
dp[c] = 1;
}
for (int i = 1; i <= amount; i++) {
for (int j = 0; j < coins.size(); j++) {
if (i > coins[j] && dp[i] > dp[i - coins[j]] + 1) {
//min(dp[i - c] + 1, dp[i])
dp[i] = dp[i - coins[j]] + 1;
}
}
}
return dp[amount] == amount + 1 ? -1 : dp[amount];
}
不难看出上面的动态规划算法,其实是一种暴力破解法,时间复杂度是O( amount*coin.size()),
如果 amount 达到上万,那耗时就特别大了,很不划算,这就需要想下有没有可以优化的地方。
其实这个零钱兑换,肯定只是在 coins里面的硬币中,抽取 0 至多个 同种硬币,抽多种。我们
困惑的地方在于,到底抽多少个,这个是不确定。想到这里,可以用回溯来解决的这个问题。
首先,了解一下回溯算法的基本结构:
void backtrace(int t)
{
if(t > N){
//大于边界值,不在递归
...
}else{
for(auto l : list){
... //做出选择,或操作
backtrace(t + 1); //做出选择后,递归
... // 撤销选择,或操作
}
}
}
回溯算法,其实就是 深度优先遍历算法 加 剪枝操作,该算法可以遍历问题的所有选项。
凑硬币的动态规划转移方程是:dp[i] = min(dp[i - c] + 1, dp[i]),这里可以用回溯遍历,每个硬币金额在dp[amount]中所需要的数量,回溯会存在多个dp[amount], 取最小的那个即可。即:
dp[i] = min(dp[i - c*j] + j, dp[i]), 其中 j的范围是(0 ~ i / c)
下面是动态规划 + 回溯 实现的c++代码:
void helper(vector<int>& coins, int start, int target, int cur, int &res)
{
if (start < 0) return;
if (target % coins[start] == 0) {
res = min(res, cur + target / coins[start]);
return;
}
for (int i = target / coins[start]; i >= 0; i--) {
//这个判断很精髓,特别的细,首先 cur + i >= res 肯定是成立的,其次 target % coins[start] != 0,所以后面至少还会存在一次,所以用cur + i >= res - 1
if (cur + i >= res - 1) break;
//金额减少i * coins[start], 硬币数增加 i, i 可等于0(=0就是该硬币金额不算入其中)
helper(coins, start - 1, target - i * coins[start], cur + i, res);
}
}
int coinChange(vector<int>& coins, int amount)
{
int res = INT_MAX;
sort(coins.begin(), coins.end());
helper(coins, coins.size() - 1, amount, 0, res);
return res == INT_MAX ? -1 : res;
}
第一次在csdn写算法题的题解,可能讲的不太好,如果大家感兴趣,但又不太明白的,可以留言给我。