零钱兑换

175 阅读2分钟

题目

给你一个整数数组 coins ,表示不同面额的硬币;以及一个整数 amount ,表示总金额。 计算并返回可以凑成总金额所需的 最少的硬币个数 。如果没有任何一种硬币组合能组成总金额,返回 -1 。 你可以认为每种硬币的数量是无限的。

示例:

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

暴力递归

class Solution {
    public int coinChange(int[] coins, int amount) {
        return dp(coins, amount);

    }
    int dp(int[] coins, int amount){
        if(amount == 0) return 0;
        if(amount < 0) return -1;
        int res = Integer.MAX_VALUE;
        for(int coin : coins){
            int subProblem = dp(coins, amount - coin);
            if(subProblem == -1) continue;
            res = Math.min(res, subProblem + 1);
        }
        return res == Integer.MAX_VALUE ? -1 : res;
    }
}

首先这个问题属于动态规划问题,因为它具有最优子结构。要符合最优子结构子问题之间必须相互独立。 假设面值为1,2,5想求amount = 11的最少硬币书,只需要知道amount = 10 9 6的最少硬币数(子问题),然后把子问题加一即可。 同斐波那契数列,暴力破解后仍有大部分计算重复计算需要减少计算量,假设目标金额为K,给定硬币数为K,那么最坏的情况为全是一元硬币,树的高度为N,假设为满K叉树,则节点总数为O(kNk^N),由于每次递归包含一个for循环,则时间复杂度为O(KN+1K^{N+1})。


带"备忘录"的递归

类似之前斐波那契数列。

class Solution {
    int[] memo;
    public int coinChange(int[] coins, int amount) {
        memo = new int[amount + 1];
        Arrays.fill(memo, -2);
        return dp(coins, amount);

    }
    int dp(int[] coins, int amount){
        if(amount == 0) return 0;
        if(amount < 0) return -1;
        if(memo[amount] != -2) return memo[amount];
        int res = Integer.MAX_VALUE;
        for(int coin : coins){
            int subProblem = dp(coins, amount - coin);
            if(subProblem == -1) continue;
            res = Math.min(res, subProblem + 1);
        }
        memo[amount] = (res == Integer.MAX_VALUE) ? -1 : res;
        return memo[amount];
    }
}

dp数组的迭代解法

也可以使用自底向上的DP数组来消除重叠子问题。DP数组的定义,当目标为i时,至少需要dp[i]个硬币凑出目标金额。

class Solution {
    public int coinChange(int[] coins, int amount) {
       int[] dp = new int[amount+1];
       Arrays.fill(dp, amount+1);
       dp[0] = 0;
       for(int i = 0; i < dp.length; i++){
           for(int coin : coins){
               if(i - coin < 0){
                   continue;
               }
               dp[i] = Math.min(dp[i], 1 + dp[i - coin]);
           }
       } 
        return (dp[amount] == amount + 1) ? -1 : dp[amount];
    }
}

ps:因为凑成amount金额的硬币数最多为amount,所以数组的初始值为amount+1,为什么不直接初始化为Integer.MAX_VALUE,因为后面又dp[i - coin] + 1,导致整形溢出。


解题思路参考labuladong的算法笔记1.3.2章。