LeetCode 322. Coin Change

问题描述

这里写图片描述

问题描述

  • 经典的凑硬币问题:给定不同面值的硬币,问至少需要多少枚硬币,可以凑成指定的金额。如果无法凑成,返回 -1,注意,每一硬币可以无限次使用。
  • 因为每一硬币可以无限次使用,所以这是一个完全背包问题,同时,这也类似于组合问题,和 LeetCode 39. Combination Sum 类似,只不过39需要返回所有的具体路径,要用 dfs + 回溯。而该题是一个最优问题(最优子结构),用动态规划做。
  • dp[i][j] :表示用[0 ~ i]中的数字加出j用到的最少硬币的数目,如果dp[i][j] = Integer.MAX_VALUE ,表示凑不出硬币。
  • 既然是完全背包 + 最优子结构,那么它的状态转移条件是: dp[i][j] = Math.min (dp[i - 1][j], dp[i][j - coins[i]]), i - 1 >= 0, j - coins[j] >= 0,在用不用coins[i]时取最小的一种情况,因为是完全背包,所以coins[i]可以多次使用, 所以是dp[i][j - coins[i]],如果只能使用一次,那么便是普通的背包问题,那么便是dp[i - 1][j - coins[i]]
  • 也可以用另一种逻辑: dp[i][j] : 表示用coins中前i个数字加出j用到的最少硬币的数目

经验教训

  • 完全背包问题的特点
  • 最优子结构问题的特点
  • 该题与组合问题的联系与区别,进而到DFS 与 动态规划的联系与区别

代码实现

  • 普通动态规划
    public int coinChange(int[] coins, int amount) {
        if (coins == null || coins.length == 0) {
            return 0;
        }
        //dp[i][j] 表示用[0 ~ i]中的数字加出j用到的最少硬币的数目
        int[][] dp = new int[coins.length][amount + 1];
        //初始化第一行
        dp[0][0] = 0;
        for (int j = 1; j < amount + 1; ++j) {
            dp[0][j] = j % coins[0] == 0 ? j / coins[0] : Integer.MAX_VALUE;
        }
        for (int i = 1; i < coins.length; ++i) {
            dp[i][0] = 0;
            for (int j = 1; j < amount + 1; ++j) {
                //两者取其小            
                dp[i][j] = dp[i - 1][j];
                if (j - coins[i] >= 0) {
                    dp[i][j] = Math.min(dp[i][j], dp[i][j - coins[i]] == Integer.MAX_VALUE ? Integer.MAX_VALUE: 1 + dp[i][j - coins[i]]);
                }
            }
        }
        return dp[coins.length - 1][amount] == Integer.MAX_VALUE ? -1 : dp[coins.length - 1][amount];
    }
  • 动态规划优化空间(注意优化点,不只是优化了空间)
    public int coinChange(int[] coins, int amount) {
        if (coins == null || coins.length == 0) {
            return 0;
        }
        int[] dp = new int[amount + 1];
        //初始化该一维数组(相当于 i == 0 的情况)
        dp[0] = 0;
        for (int j = 1; j < amount + 1; ++j) {
            dp[j] = j % coins[0] == 0 ? j / coins[0] : Integer.MAX_VALUE;
        }
        //滚动更新该一维数组
        for(int i = 1; i < coins.length; ++i) {
            for (int j = coins[i]; j < amount + 1 ; ++j) {
                    dp[j] = Math.min(dp[j], dp[j - coins[i]] == Integer.MAX_VALUE ? Integer.MAX_VALUE : 1 + dp[j - coins[i]]);
            }
        }
        return dp[amount] == Integer.MAX_VALUE ? -1 : dp[amount];
    }
  • discuss高票做法,但个人认为有缺陷
    public int coinChange(int[] coins, int amount) {
        if (coins == null || coins.length == 0) {
            return 0;
        }
        int max = amount + 1;
        int[] dp = new int[max];
        dp[0] = 0;
        for (int j = 0; j < max; ++j) {
            dp[j] = j % coins[0] == 0 ? j / coins[0] : max;
        }
        for (int i = 0; i < coins.length; ++i) {
            for (int j = coins[i]; j < max; ++j) {
                    dp[j] = Math.min(dp[j], dp[j - coins[i]] + 1);
            }
        }
        return dp[amount] > amount ? -1 : dp[amount];
    }
  • 另一种逻辑
     public int coinChange(int[] coins, int amount) {
        if (coins == null || coins.length == 0) {
            return 0;
        }
        //dp[i][j] 表示用coins中前i个数字加出j用到的最少硬币的数目
        int[][] dp = new int[coins.length + 1][amount + 1];
        //初始化第一行
        dp[0][0] = 0;
         for (int j = 1; j < amount + 1; ++j) {
             dp[0][j] = Integer.MAX_VALUE;
         }
         for (int i = 1; i < coins.length + 1; ++i) {
             dp[i][0] = 0;
             for (int j = 1; j < amount + 1; ++j) {
                 dp[i][j] = dp[i - 1][j];
                 if (j - coins[i - 1] >= 0) {
                    dp[i][j] = Math.min(dp[i][j], dp[i][j - coins[i - 1]] == Integer.MAX_VALUE ? Integer.MAX_VALUE: 1 + dp[i][j - coins[i - 1]]);
                } 
             }
         }
         return dp[coins.length][amount] == Integer.MAX_VALUE ? -1 : dp[coins.length][amount];
     }

猜你喜欢

转载自blog.csdn.net/zjxxyz123/article/details/80203984