leetcode 322 零钱兑换问题

Leetcode 322题 零钱兑换问题

个人博客中有本文原文https://whatghost.github.io/2020/03/12/leetcode-322/
如果喜欢可以访问个人博客WhatGhost.github.io,会有更多分享内容

题目及实例

给定不同面额的硬币 coins 和一个总金额 amount。编写一个函数来计算可以凑成总金额所需的最少的硬币个数。如果没有任何一种硬币组合能组成总金额,返回 -1。

示例 1:

输入: coins = [1, 2, 5], amount = 11
输出: 3 
解释: 11 = 5 + 5 + 1
示例 2:

输入: coins = [2], amount = 3
输出: -1

这个题目乍一看像是贪心算法,但是贪心算法容易陷入局部最优,如coins=[1,2,7,10],全局最优解应为[7,7],但是谈心会陷入局部最优解[2,2,10];

零钱问题曾经是我大二算法课考试的题目,一般会采用动态规划的解题思路,但是在leetcode的题解中看到了曾经我觉得性能可能会很差的回溯(+剪枝)的方法竟有很不错的性能,给了我很大的启发,所以下面我回把两个方法都介绍一边

动态规划

解析

动态规划其实是一个填表的过程,数组dp是一个长度为amount+1的数组(为什么要加1,因为钱数从0到amount有amount+1个数),其中第i个元素代表如果需要找i块钱需要的最少硬币数,这样一直算到最后,dp[amount]就是我们需要的答案。

那怎么保证从前往后的每一步都能得出当前的最少数目呢
首先我们把数组都初始化成一个最大值(这里用的是amount+1,因为数组中的元素不可能比amount大)
第一个元素当需要找的钱为0时,个数肯定是0
当找零数为i时,进行以下操作

for i:1 to coins.size() do
begin
    dp[i] = min(dp[i], dp[i - coins[j]] + 1);
end

其实就是遍历coins列表,找到可以使当前数最小的值,dp[i]是原始值,代表不使用coins[j]这个硬币,dp[i - coins[j]] + 1代表使用这个硬币,计算里面的最小值,这样就保证了我每一个dp[i]都会是全局最优的情况,所以最后的结果也是全局最优

动态规划代码

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

    }
};

回溯+剪枝

解析

这个算法只有回溯的话性能会很差,但是加上剪枝操作后会带来很多的性能提升,在提交中也取得了很好的结果,给了我很大的启发,这里要感谢leetcode上的Ikaruga的思路和代码
这个就是很简单的思想,我一个一个试,如果不成就回溯,如果成了就记录下当前的方案的结果,用一个变量记录全局的最优质的,每一个结果都会跟当前的最好的值比较,如果新得出的结果最好就更新全局的值

但是问题在于这样其实是很耗时的,如果我很早的就得出了最优的值那我后面还会做很多无谓的操作,这里剪枝的用处就出现了,下面的代码中用 k + count < ans 这个判断来进行了剪枝的操作,如果当前的数目已经没有成为最优解的可能性了,就不会再往下进行搜索。

这样最好情况下很快就能完成遍历,这其实就是一种贪心的思想,这也是提交结果很好的原因,因为很多情况下贪心算出的局部最优解就是全局最优。但是最坏情况下需要遍历所有的可能性,时间也会比动态规划长。

代码

class Solution {
public:
    void coinChange(vector<int>& coins, int amount, int c_index, int count, int& ans)
    {
        if (amount == 0)
        {
            ans = min(ans, count);
            return;
        }
        if (c_index == coins.size()) return;

        for (int k = amount / coins[c_index]; k >= 0 && k + count < ans; k--)
        {
            coinChange(coins, amount - k * coins[c_index], c_index + 1, count + k, ans);
        }
    }

    int coinChange(vector<int>& coins, int amount)
    {
        if (amount == 0) return 0;
        sort(coins.rbegin(), coins.rend());
        int ans = INT_MAX;
        coinChange(coins, amount, 0, 0, ans);
        return ans == INT_MAX ? -1 : ans;
    }

};
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值