[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

原文地址:https://www.cnblogs.com/zaynq/p/12679080.html