322. Coin Change

50 篇文章 4 订阅
41 篇文章 2 订阅

You are given coins of different denominations and a total amount of money amount. Write a function to compute the fewest number of coins that you need to make up that amount. If that amount of money cannot be made up by any combination of the coins, return -1.

Example 1:

Input: coins = [1, 2, 5], amount = 11
Output: 3 
Explanation: 11 = 5 + 5 + 1

Example 2:

Input: coins = [2], amount = 3
Output: -1

Note:
You may assume that you have an infinite number of each kind of coin.

Accepted

180.9K

Submissions

607K

 

思路一(超时)

采用DFS暴力搜索,采用先降序排序,后从大到小DFS的搜索方法,但该方法搜索结果并不是最优解,即尽量使用最大值的方法并不一定拥有最少的硬币个数;改正后使用暴力DFS所有排列结果,由于递归太多,严重超时

代码一:

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        int res = INT_MAX;
        dfs(coins, 0, 0, amount, res);
        if (res == INT_MAX) return -1;
        return res;
    }

    void dfs(vector<int>& coins, int cnt, long tmp, long amount, int& res) {
        if (tmp == amount) {
            res = min(res, cnt);
            return;
        } else if (tmp > amount) {
            return;
        } else {
            for (size_t i = 0; i < coins.size(); ++i) {
                dfs(coins, cnt+1, tmp+coins[i], amount, res);
            }
        }
        return;
    }
};

思路二:

动态规划(DP),不会超时。

如果大家刷题有一阵子了的,那么应该会知道,对于求极值问题,我们还是主要考虑动态规划Dynamic Programming来做,好处是保留了一些中间状态的计算值,这样可以避免大量的重复计算。我们维护一个一维动态数组dp,其中dp[i]表示钱数为i时的最小硬币数的找零,注意由于数组是从0开始的,所以我们要多申请一位,数组大小为amount+1,这样最终结果就可以保存在dp[amount]中了。初始化dp[0] = 0,因为目标值若为0时,就不需要硬币了。其他值可以初始化是amount+1,为啥呢,因为最小的硬币是1,所以amount最多需要amount个硬币,amount+1也就相当于当前的最大值了,注意这里不能用整型最大值来初始化,因为在后面的状态转移方程有加1的操作,有可能会溢出,除非你先减个1,这样还不如直接用amount+1舒服呢。好,接下来就是要找状态转移方程了,没思路?不要紧!回归例子1,假设我取了一个值为5的硬币,那么由于目标值是11,所以是不是假如我们知道dp[6],那么就知道了组成11的dp值了?所以我们更新dp[i]的方法就是遍历每个硬币,如果遍历到的硬币值小于i值(比如我们不能用值为5的硬币去更新dp[3])时,我们用 dp[i - coins[j]] + 1 来更新dp[i],所以状态转移方程为:

dp[i] = min(dp[i], dp[i - coins[j]] + 1);

其中coins[j]为第j个硬币,而i - coins[j]为钱数i减去其中一个硬币的值,剩余的钱数在dp数组中找到值,然后加1和当前dp数组中的值做比较,取较小的那个更新dp数组。先来看迭代的写法如下所示:

代码二:

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        vector<int> dp(amount+1, amount+1);
        dp[0] = 0;
        for (int i = 1; i <= amount; ++i) {
            for (int j = 0; j < coins.size(); ++j) {
                if (coins[j] <= i) {
                    dp[i] = min(dp[i], dp[i-coins[j]] + 1);
                }
            }
        }
        return dp[amount] == amount+1 ? -1 : dp[amount];
    }
};

思路三:

难道这题一定要DP来做吗,我们来看网友hello_world00提供的一种解法,这其实是对暴力搜索的解法做了很好的优化,不仅不会TLE,而且击败率相当的高!对比Brute Force的方法,这里在递归函数中做了很好的优化。首先是判断start是否小于0,因为我们需要从coin中取硬币,不能越界。下面就是优化的核心了,看target是否能整除coins[start],这是相当叼的一步,比如假如我们的目标值是15,如果我们当前取出了大小为5的硬币,我们做除法,可以立马知道只用大小为5的硬币就可以组成目标值target,那么我们用cur + target/coins[start] 来更新结果res。之后的for循环也相当叼,不像暴力搜索中的那样从start位置开始往前遍历coins中的硬币,而是遍历 target/coins[start] 的次数,由于不能整除,我们只需要对余数调用递归函数,而且我们要把次数每次减1,并且再次求余数。举个例子,比如coins=[1,2,3],amount=11,那么 11除以3,得3余2,那么我们的i从3开始遍历,这里有一步非常有用的剪枝操作,没有这一步,还是会TLE,而加上了这一步,直接击败百分之九十九以上,可以说是天壤之别。那就是判断若 cur + i >= res - 1 成立,直接break,不调用递归。这里解释一下,cur + i 自不必说,是当前硬币个数cur 加上新加的i个硬币,我们都是知道cur+i如果大于等于res的话,那么res是不会被更新的,那么为啥这里是大于等于res-1呢?因为能运行到这一步,说明之前是无法整除的,那么余数一定存在,所以再次调用递归函数的target不为0,那么如果整除的话,cur至少会加上1,所以又跟res相等了,还是不会使得res变得更小。解释到这里应该比较明白了吧,有疑问的请在下方留言哈,参见代码如下:

代码三:

class Solution {
public:
    int coinChange(vector<int>& coins, int amount) {
        int res = INT_MAX, n = coins.size();
        sort(coins.begin(), coins.end());
        helper(coins, n - 1, amount, 0, res);
        return (res == INT_MAX) ? -1 : res;
    }
    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) {
            if (cur + i >= res - 1) break;  // 剪枝,优化递归深度
            helper(coins, start - 1, target - i * coins[start], cur + i, res);
        }
    }
};

总的来说,这一题是考察动态规划,其它答案做起来并不自然顺畅。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值