LeetCode Top 100 Liked Questions 322. Coin Change (Java版; Medium)

welcome to my blog

LeetCode Top 100 Liked Questions 322. Coin Change (Java版; Medium)

题目描述
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
class Solution {
    public int coinChange(int[] coins, int amount) {
        if(coins.length==0){
            return -1;
        }
        //dp[i]表示目标值为i时拼凑所需的最少硬币数
        //dp[i] = min(dp[i-coins[0]], dp[i-coins[1]]) + 1   dp[i]!=-1
        int[] dp = new int[amount+1];
        int n = dp.length;
        Arrays.fill(dp, -1);
        dp[0] = 0;
        for(int i=1; i<n; i++){
            for(int c : coins){
                if(i-c>=0 && dp[i-c]!=-1){
                    if(dp[i]==-1){
                        dp[i] = dp[i-c] + 1;
                    }else{
                        dp[i] = Math.min(dp[i], dp[i-c] + 1);
                    }
                }
            }
        }
        return dp[n-1];
    }
}
第二次做; 暴力递归改动态规划
class Solution {
    public int coinChange(int[] coins, int amount) {
        if(coins==null || coins.length==0)
            return -1;
        //dp[i]表示总金额为i时, 凑成i所需硬币的最少数量
        int[] dp = new int[amount+1];
        Arrays.fill(dp, Integer.MAX_VALUE);
        dp[0]=0;
        for(int i=1; i<=amount; i++){
            for(int coin : coins){
                if(i<coin)
                    continue;
                if(dp[i-coin]==Integer.MAX_VALUE)
                    continue;
                dp[i] = Math.min(dp[i], dp[i-coin]+1);
            }
        }
        return dp[amount]==Integer.MAX_VALUE? -1 : dp[amount];
    }
}
第一次做; 暴力递归改成动态规划; 要注意很多细节的处理, 尤其是初始化!
/*
暴力递归改dp
*/
import java.util.Arrays;

class Solution {
    public int coinChange(int[] coins, int amount) {
        if(coins==null || coins.length==0 || amount<0)
            return -1;
        //细节:对数组排序
        Arrays.sort(coins);
        //核心1:dp[i]表示amount为i时的最优解
        int[] dp = new int[amount+1];
        //核心2:dp填充为Integer.MAX_VALUE, 初始化每个状态, 表示状态没有解
        Arrays.fill(dp,Integer.MAX_VALUE);
        dp[0] = 0;
        //核心3:初始化一定要想清楚; 初始化太核心了!
        //从0开始计数, 不是从Integer.MIN_VALUE开始计数
        int count=0;
        //核心4:i的含义是amount为i时
        for(int i=1; i<=amount; i++){
            for(int j=0; j<coins.length; j++){
                if(i<coins[j])
                    break;
                //amount>=coin[j]
                //核心5:注意将i-coins[j]与dp[]结合起来理解
                //核心6:dp[i-coins[j]]无解时直接进行下一轮循环
                if(dp[i-coins[j]]==Integer.MAX_VALUE)
                    continue;
                dp[i] = Math.min(dp[i], dp[i-coins[j]]+1);
            }
        }
        //核心7
        return dp[amount]==Integer.MAX_VALUE? -1 : dp[amount];
    }
}
第一次做; 暴力递归优化, 使用memo数组, 取消了count参数, 从而让count表示当前rest的结果; 初始化真重要; 而且我现在知道了为什么没能把LC279改成动态规划, 因为我对可变参数的定义不明确, 更准确地说我没有把可变参数和返回的结果区分开, 我把结果也当做递归函数的参数并误以为结果也是可变参数, 所以当时就感觉结果这个变量没有范围啊? 现在才明确过来. 这道题的暴力递归版本我也把结果当做递归函数的参数了, 之后再写的暴力递归的时候, 试着不把结果写在递归函数参数中, 把结果写在递归函数参数中的缺点是: 不能使用memo数组优化暴力递归, memo索引的含义和结果不匹配; 大局意识: 要掌握流程的框架, “到这里已经计算完了凑成rest的所有可能”. 很多经验需要靠刷题去理解体会
/*
使用memo数组优化暴力递归, 避免重复计算相同子问题
*/
import java.util.Arrays;

class Solution {
    public int coinChange(int[] coins, int amount) {
        if(coins==null || coins.length==0 || amount<0)
            return -1;
        Arrays.sort(coins);
        int[] memo = new int[amount+1];
        Arrays.fill(memo, Integer.MAX_VALUE);
        int res = core(coins, memo, amount);
        return res==Integer.MAX_VALUE? -1:res;
    }
    public int core(int[] coins, int[] memo, int rest){
        //base case
        if(rest == 0)
            return 0;
        //计算过的就不用再算了, 避免重复子问题的计算
        if(memo[rest] != Integer.MAX_VALUE)
            return memo[rest];
        int ans = Integer.MAX_VALUE;
        for(int i=0; i<coins.length; i++){
            //之前已经对coins排序了, 所以如果coins[i] > rest, 那么后面的i也没有必要判断了
            if(coins[i] > rest)
                break;
            int curr = core(coins, memo, rest-coins[i]);
            //检查新条件新递归返回值的有效性; 无效的话直接break; 
            //curr==-1说明for循环中i=0时就因为coins[i] > rest而break了, 说明core(coins, memo, rest-coins[i])无解, 进入下一轮循环尝试下一个i
            if(curr==-1)
                continue;
            //1表示当前这枚硬币
            ans = Math.min(ans, curr + 1);
        }
        //到这里已经计算完了凑成rest的所有可能;到这里已经计算完了凑成rest的所有可能;到这里已经计算完了凑成rest的所有可能
        //到这里已经计算完了凑成rest的所有可能, 如果ans还是Integer.MAX_VALUE的话, 说明凑不出rest, 令memo[rest]=-1; 我最开始没有注意到这个细节
        memo[rest] = ans==Integer.MAX_VALUE?-1:ans;
        //刚才错写成ans了!
        return memo[rest];
    }
}
暴力递归; 超时77/182; 核心: 不要把题目的最终结果作为递归函数的参数, 而是要作为递归函数的返回值
class Solution {
    public int coinChange(int[] coins, int amount) {
        //input check
        if(coins==null || coins.length==0)
            return -1;
        Arrays.sort(coins);
        int res = core(coins, coins.length-1, amount);
        return res;
    }
    
    //递归函数逻辑: 针对当前总金额amount, 选一枚面额小于等于amount的最大的硬币(当前条件); 获取剩余的amount需要多少个硬币凑成(新条件新递归); 返回当前amount一共需要几枚硬币凑成
    private int core(int[] coins, int index, int amount){
        //base case
        if(amount==0)
            return 0;
        if(amount<0)
            return -1;
        //amount > 0
        int res = Integer.MAX_VALUE;
        //每次尽可能选择面额大的硬币, 这样才能得到最少的count
        for(int i=index; i>=0; i--){
            int cur = core(coins, i, amount-coins[i]);
            if(cur==-1)
                continue;
            res = Math.min(res, cur+1);
            
        }
        return res==Integer.MAX_VALUE? -1 : res;
    }
}
第一次做;暴力递归,超时15/182; 这个暴力递归写的非常不好, 最不好的一点就是: 把最终结果作为递归函数的参数, 不要这么写!
/*
暴力递归
要想使用最少的张数得到amount, 就要尽可能选择面额大的硬币
*/
import java.util.Arrays;

class Solution {
    public int coinChange(int[] coins, int amount) {
        if(coins==null || coins.length==0 || amount < 0)
            return -1;
        //对coins排序, 按照面值从小到大排序
        Arrays.sort(coins);
        int res = core(coins, amount, 0);
        return res==Integer.MAX_VALUE?-1:res;
        
    }
    public int core(int[] coins, int rest, int count){
        //base case
        if(rest==0)
            return count;
        //循环中可以杜绝这种情况发生, 就不用单独判断了
        // if(rest<0)
            // return -1;
        //here, rest>0
        int ans = Integer.MAX_VALUE;
        int n = coins.length;
        //从面值最大的硬币开始选; 尽可能多地使用面值大的硬币
        for(int i=n-1; i>=0; i--){
            if(coins[i]>rest)
                continue;
            int curr = core(coins, rest-coins[i], count+1);
            ans = Math.min(ans, curr);
        }
        return ans;
    }
}
leetcode上的最优解: 递归+memo
public class Solution {
public int coinChange(int[] coins, int amount) {
    if(amount<1) return 0;
    return helper(coins, amount, new int[amount]);
}

private int helper(int[] coins, int rem, int[] count) { // rem: remaining coins after the last step; count[rem]: minimum number of coins to sum up to rem
    if(rem<0) return -1; // not valid
    if(rem==0) return 0; // completed
    if(count[rem-1] != 0) return count[rem-1]; // already computed, so reuse
    int min = Integer.MAX_VALUE;
    for(int coin : coins) {
        int res = helper(coins, rem-coin, count);
        if(res>=0 && res < min)
            min = 1+res;
    }
    count[rem-1] = (min==Integer.MAX_VALUE) ? -1 : min;
    return count[rem-1];
}
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值