• [LeetCode每日1题][中等] 322. 零钱兑换


    题目

    题目链接
    在这里插入图片描述

    DFS+贪心+剪枝解法

    思路

    一看到这种组合问题,就想到了DFS,并且想到了一个优化点:对coins排序,从大到小进行搜索,搜索到一种解法,直接返回结果,也就是结合贪心法。

    示例1:

    coins = [1,2,5] amount = 11

    先排序成 [5,2,1],然后进行DFS:

    1. 需要5的个数为 11/5 = 2,还剩下11-2*5 = 1
    2. 需要2的个数为 1/2 = 0,还剩下1-2*0 = 1
    3. 需要1的个数为 1/1 = 1,还剩下1-1*1 = 0
    4. 得到一个解 2+0+1 = 3,算法结束

    嗯,似乎没什么问题,但是交了才发现,对于有一些组合,贪心并不能求得最优解。

    示例2:

    coins = [1,7,10] amount = 14

    会发现10+1+1+1这样的组合,会比7+7更早搜索到。

    于是修改代码,搜索到一个解的时候先不返回,把解保存起来,然后继续搜索,遇到更小的解时更新结果。

    修改后的运行过程:

    1. 需要10的个数为 14/10 = 1,还剩下14-1*10 = 4
    2. 需要7的个数为 4/7 = 0,还剩下4
    3. 需要1的个数为 4/1 = 4,还剩下4-4*1 = 0
    4. 得到一个解 1+0+4 = 5,保存结果
    5. 需要10的个数为 14/10 - 1 = 0,还剩下14
    6. 需要7的个数为 14/7 = 2,还剩下0
    7. 得到一个解 0+2 = 2,比原来的解小,更新结果
      ……

    等等,那这个不就和暴力没什么差别了?不管了先交,果然超时。

    为了剪枝,冥思苦想了一个多小时,依然没有头绪,直接点开题解,发现了一个剪枝方法,关键在于:
    如果(当前步数 + 当前需要的硬币数) > 已保存的结果,退出当前搜索过程。(下方代码中for循环的第二个条件) 如上所述,贪心得到的解也许不是最优的,但用来剪枝效果还是很好的。

    实现

    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;
    }
    
    作者:ikaruga
    链接:https://leetcode-cn.com/problems/coin-change/solution/322-by-ikaruga/
    来源:力扣(LeetCode)
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    

    DP解法(自上而下)

    思路

    先偷一张官方题解的图
    在这里插入图片描述

    • F(S):组成金额 SS 所需的最少硬币数量
    • c[i]:硬币

    由于每次递归只用一个硬币做尝试,最终结果是下一层的最小值+1。
    可以看到,对于大多数n,F[n]被重复计算,可以把它存在一个数组,下次遇到的时候直接返回即可。

    实现

    class Solution {
        vector<int>count;
        int dp(vector<int>& coins, int rem) {
            if (rem < 0) return -1;
            if (rem == 0) return 0;
            if (count[rem - 1] != 0) return count[rem - 1];
            int Min = INT_MAX;
            for (int coin:coins) {
                int res = dp(coins, rem - coin);
                if (res >= 0 && res < Min) {
                    Min = res + 1;
                }
            }
            count[rem - 1] = Min == INT_MAX ? -1 : Min;
            return count[rem - 1];
        }
    public:
        int coinChange(vector<int>& coins, int amount) {
            if (amount < 1) return 0;
            count.resize(amount);
            return dp(coins, amount);
        }
    };
    
    作者:LeetCode-Solution
    链接:https://leetcode-cn.com/problems/coin-change/solution/322-ling-qian-dui-huan-by-leetcode-solution/
    来源:力扣(LeetCode)
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    

    DP解法(自下而上)

    思路

    依然偷官方图
    在这里插入图片描述
    故名思意,就是计算F(n)时从最底层开始,好处是不再需要递归。

    实现

    class Solution {
    public:
        int coinChange(vector<int>& coins, int amount) {
            int Max = amount + 1;
            vector<int> dp(amount + 1, 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-Solution
    链接:https://leetcode-cn.com/problems/coin-change/solution/322-ling-qian-dui-huan-by-leetcode-solution/
    来源:力扣(LeetCode)
    著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    

    C++复习

    std::sort的用法

    正向排序 std::sort(arr.begin(), arr.end())
    反向排序 std::sort(arr.rbegin(), arr.rend())

    其他

    • 当需要引用传递的时候,在形参类型后加上一个&,传值的时候不需要。
    • std::vector::resize() 可以用于调整vector容量
    • int最值分别定义为INT_MAXINT_MIN

    参考

    官方题解
    剪枝法 by ikaruga

  • 相关阅读:
    TreeSet里面放对象,如果同时放入了父类和子类的实例对象,那比较时使用的是父类的compareTo方法,还是使用的子类的compareTo方法,还是抛异常?
    当一个线程进入一个对象的一个synchronized方法后,其它线程是否可进入此对象的其它方法?
    @Qualifier 注解?
    Set里的元素是不能重复的,那么用什么方法来区分重复与否呢?是用==还是equals()?它们有何区别?
    Filter是什么?有什么作用?
    List和 Map区别?
    Spring框架的事务管理有哪些优点?
    Listener是什么?有什么作用?
    字节流与字符流的区别?
    Struts2里面有什么隐式对象?
  • 原文地址:https://www.cnblogs.com/zaynq/p/12679080.html
Copyright © 2020-2023  润新知