[LeetCode解题] -- 零钱兑换

动态规划是 求解最优化问题的一种常用策略

零钱兑换   322. 零钱兑换

[ 题目描述 ]

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

你可以认为每种硬币的数量是无限的。

 

示例 1:

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

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

输入:coins = [1], amount = 0
输出:0
示例 4:

输入:coins = [1], amount = 1
输出:1
示例 5:

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

一、贪心求解

贪心只看眼前最优,但不一定得到全局最优解。如:41 = 25 10 5 1 最优解 20 20 1

如果没有如下限制

1. 如果面值从1开始,此时一定有解(最坏情况 零钱面值都是1)

则此时如下条件一定可以不满足: 如果没有任何一种硬币组合能组成总金额,返回 -1。( 即如果amount数额凑不齐,返回 -1 ) 

2. coins 数组逆序排序

[ 解题思路 ]

如下解题思路为解决 数组逆序排序问题,可忽略

数组逆序排序,需要 int 是包装类型 Integer

1. Collections.reverseOrder()

// 可以用此方法进行逆序排序,但是int数组必须是Integer包装类 类型
Arrays.sort(coin, Collections.reverseOrder());

2. 传入比较器

// 简写如下
Arrays.sort(coins, (Integer f1, Integer f2) -> f2 - f1); // 传入比较器,逆序排序  {25, 5, 10, 1}

即
Arrays.sort(coins,(Integer f1,Integer f2)->{
		return f1-f2;
});

当然可以先将其转为包装类

Integer[] coin = new Integer[coins.length];
for (int i = 0; i <coins.length ; i++) {
	coin[i]=coins[i];
}

[ 代码实现 ]

    static int  coinChange1(int[] coins, int amount) {
        int res = 0, i = 0;         // res面值可用数量 i 第i个
        while (i < coins.length) {
            if (amount < coins[i]) {	//逆序,先取出最大的face[0]=25  剩下的钱 < 当前值。面值不可用
                i++;        // 面值不可用数量
                continue;
            }
            amount -= coins[i]; // 面值是可用的
            res++;
        }
        return res;
    }


[ 性能分析 ]

 

二、递归求解

如果没有如下限制

1. 如果没有任何一种硬币组合能组成总金额,返回 -1。( 即如果amount数额凑不齐,返回 -1 ) 

[ 解题思路 ]

参考 斐波那契

//	 此处类别 fib  1. fib有2个选项 零钱兑换有4个选项  2.都有重复计算--记忆化搜索 优化
	static int fib(int n){
		if(n<=2)
			return 1;
		return fib(1)+fib(2);
	}

[ 代码实现 ]

解法1 暴力递归

    /**
     * 暴力递归(自顶向下的调用,出现了重叠子问题)
     *
     * 贪心策略得到的并非最优解 只看眼前
     *
     * dp(20) 凑到20分需要的最少硬币个数
     *
     * dp(41) 凑到41分需要的最少硬币个数
     *
     * dp(n)  凑到n分需要的最少硬币个数
     *
     * 第一次选择了25分的硬币,现在还差 n-25分
     * 	dp[n-25]凑到n-25需要的最少硬币个数
     * 如果第一次选择的了25分的硬币,那么 dp(n)=dp(n-25)+1 // +1,已经选择了1枚硬币,25分的硬币
     */
    static int coinChange2_(int n) {
        if (n < 1) return Integer.MAX_VALUE;
        if (n == 25 || n == 20 || n == 5 || n == 1) return 1;
        int min1 = Math.min(coinChange2_(n - 25), coinChange2_(n - 20));
        int min2 = Math.min(coinChange2_(n - 5), coinChange2_(n - 1));
        return Math.min(min1, min2) + 1;
    }

解法2 记忆化搜索优化

    /**
     * 记忆化搜索(自顶向下的调用)
     *  dp[1]=dp[5]=dp[20]=dp[25]=1 凑够1、5、20、25分需要的最少硬币个数都是 1 枚
     */
    static int coinChange2(int n) {
        if (n < 1) return -1;
        int[] dp = new int[n + 1];		// 需要计算到凑过n分,下标要取到n,故长度为n+1
        int[] faces = {1, 5, 20, 25};
        for (int face : faces) {	// dp[1]=dp[5]=dp[20]=dp[25]=1; 直接赋值有bug。如果n=20,dp[25]越界
            if (n < face) break;
            dp[face] = 1;
        }
        return coinChange2(n, dp);
    }

    static int coinChange2(int n, int[] dp) {
        if (n < 1) return Integer.MAX_VALUE;
        if (dp[n] == 0) {	// 数组默认是0  dp[n] == 0 表示没有计算过
            int min1 = Math.min(coinChange2(n - 25, dp), coinChange2(n - 20, dp));
            int min2 = Math.min(coinChange2(n - 5, dp), coinChange2(n - 1, dp));
            dp[n] = Math.min(min1, min2) + 1;
        }
        return dp[n];	// 已经算过,直接返回。记忆化搜索
    }

    //	 此处类别 fib  1. fib有2个选项 零钱兑换有4个选项  2.都有重复计算--记忆化搜索 优化
    static int fib(int n){
        if(n<=2)
            return 1;
        return fib(1)+fib(2);
    }


[ 性能分析 ]

 

三 动态规划求解

[ 解题思路 ]
[ 代码实现 ]

解法1

如果没有如下条件限制

1. 如果没有任何一种硬币组合能组成总金额,返回 -1。( 即如果amount数额凑不齐,返回 -1 ) 

    /*
        dp[n] 凑够n分需要的最少硬币个数
        dp[i]=min{dp[i-1],dp[i-5],dp[i-20],dp[i-25]}+1
             解读:dp[i] 凑够i分需要的最少硬币个数 = 凑够 {,,,}最小个数,再+1个硬币
        coins={5,20,25}
        amount=5 dp=[0, 0, 0, 0, 0, 1]
        amount=6 dp=[0, -2147483648, -2147483648, -2147483648, -2147483648, 1, -2147483647]
     */
    static int coinChange3_1(int[] coins,int amount) {
        if (amount < 1 || coins == null || coins.length == 0)
            return -1;
        int[] dp = new int[amount + 1];
        for (int i = 1; i <= amount; i++) {
            int min = Integer.MAX_VALUE;
            for (int coin : coins) {
                if (i < coin)
                    continue;
                min = Math.min(dp[i-coin],min);
            }
            dp[i]=min+1;
        }
        System.out.println(Arrays.toString(dp));
        return dp[amount];
    }

解法2

    //  dp[n] 凑够n分需要的最少硬币个数
	/*
		// 如果amount数额凑不齐。即:如果没有任何一种硬币组合能组成总金额,返回 -1。
		coins={5,20,25}
		amount=5  dp=[0, -1, -1, -1, -1, 1]
		amount=6  dp=[0, -1, -1, -1, -1, 1, -1]
		amount=20 dp=[0, -1, -1, -1, -1, 1, -1, -1, -1, -1, 2, -1, -1, -1, -1, 3, -1, -1, -1, -1, 1]
	 */
    static int coinChange3_2(int[] coins,int amount) {
        if (amount < 1 || coins == null || coins.length == 0)
            return -1;
        int[] dp = new int[amount + 1];
        for (int i = 1; i <= amount; i++) {
            int min = Integer.MAX_VALUE;
            for (int coin : coins) {
                if (i < coin)	// 示例 i=1 faces={5,20,25}没有1,此时dp[1]=-1 dp[1~4]=-1
                    continue;
                int v = dp[i - coin];
                // 如果面值不是从1开始,那么凑够dp[i]需要的最少硬币个数v就是-1.即:如果{5,20,25},dp[1~4]=-1
                // 如果凑够dp[i]需要的最少硬币个数v >=min ,那么没有必要更新min.即:如果{20,5,25},amount=20
                // 		v=dp[20-20]=0 (凑够0分需要0枚);或者那么v=dp[20-15]=3 (凑够15分需要3枚)
                // 		则 v=3 答案舍弃
                if (v < 0 || v >= min)  // v=-1 即没法凑
                    continue;
                min = v;
            }
            if (min == Integer.MAX_VALUE) {	// 只要凑不齐,dp[i=-1]
                dp[i] = -1;
            } else {
                dp[i] = min + 1;
            }
        }
        System.out.println(Arrays.toString(dp));
        return dp[amount];
    }

3 完整代码

    static int coinChange(int[] coins,int amount) {
        if (amount < 1 || coins == null || coins.length == 0)
            return -1;
        int[] dp = new int[amount + 1];
        for (int i = 1; i <= amount; i++) {
            int min = Integer.MAX_VALUE;
            for (int coin : coins) {
                if (i < coin)	
                    continue;
                int v = dp[i - coin];
                if (v < 0 || v >= min)  //v < 0 即没办法凑;v >= min已经有更优解
                    continue;
                min = v;
            }
            if (min == Integer.MAX_VALUE) {	// 凑不齐,dp[i=-1]
                dp[i] = -1;
            } else {
                dp[i] = min + 1;
            }
        }
        System.out.println(Arrays.toString(dp));
        return dp[amount];
    }


[ 性能分析 ]

 

 

 

 

 

已标记关键词 清除标记
©️2020 CSDN 皮肤主题: Age of Ai 设计师:meimeiellie 返回首页