美文网首页
背包系列问题——换零钱

背包系列问题——换零钱

作者: 抬头挺胸才算活着 | 来源:发表于2020-08-14 16:52 被阅读0次

    参考资料:
    1. 动态规划之背包问题系列

    背包问题的定义参见参考资料1
    跟背包问题不同的是,目标是换的零钱的个数最少。
    零钱个数不限,所以是完全背包问题。
    因为一开始没有答案,所以初始化为INVALID,背包问题一开始初始化为0,因为背包什么都不装,0也算背包的收益。

    • 分析一迭代求法
    private int INVALID = Integer.MAX_VALUE;
    
    // 分析一 迭代求法
    public int coinChange(int[] coins, int amount){
        int[][] dp = new int[coins.length+1][amount+1];
        for (int i = 0; i < dp.length; i++) {
            Arrays.fill(dp[i], INVALID);
        }
    
        for (int i = 0; i < dp.length; i++) {
            dp[i][0] = 0;
        }
    
        for (int i = 1; i <= coins.length; i++) {
            for (int j = 1; j <= amount; j++) {
                int use;
    
                if(j >= coins[i-1]){
                    use = dp[i][j - coins[i-1]];
                    if(use != INVALID){
                        use += 1;
                    }
                }else{
                    use = INVALID;
                }
    
                dp[i][j] = Math.min(dp[i-1][j], use);
            }
        }
    
        return dp[coins.length][amount] == INVALID ? -1:dp[coins.length][amount];
    }
    
    • 分析一节省空间解法
    private int INVALID = Integer.MAX_VALUE;
    
    // 分析一 节省空间
    public int coinChange(int[] coins, int amount){
        int[] dp = new int[amount+1];
        Arrays.fill(dp, INVALID);
    
        dp[0] = 0;
    
        for (int i = 1; i <= coins.length; i++) {
            for (int j = 1; j <= amount; j++) {
                int use;
                if(j >= coins[i-1]){
                    use = dp[j - coins[i-1]];
                    if(use != INVALID){
                        use += 1;
                    }
                }else{
                    use = INVALID;
                }
                dp[j] = Math.min(dp[j], use);
            }
        }
    
        return dp[amount] == INVALID ? -1:dp[amount];
    }
    
    • 分析一递归解法
    private int INVALID = Integer.MAX_VALUE;
    
    public int coinChange(int[] coins, int amount){
        if(amount==0)
            return 0;
    
        if(coins.length==0)
            return -1;
    
        int[][] dp = new int[coins.length][amount+1];
    
        int result = coinChange(coins, amount, dp, coins.length-1);
        return result == INVALID ? -1:result;
    }
    
    private int coinChange(int[] coins, int amount, int[][] dp, int indexOfLastCoin) {
        if(dp[indexOfLastCoin][amount] != 0)
            return dp[indexOfLastCoin][amount];
    
        int use = INVALID;
        int leftAmount = amount - coins[indexOfLastCoin];
        if(leftAmount > 0){
            use = coinChange(coins, leftAmount, dp, indexOfLastCoin);
            if(use!=INVALID){
                use += 1;
            }
        } else if(leftAmount == 0){
            use = 1;
        }
    
        int notUsed = INVALID;
        int indexOfLeftCoin = indexOfLastCoin - 1;
        if(indexOfLeftCoin >= 0){
            notUsed = coinChange(coins, amount, dp, indexOfLeftCoin);
        }
    
        dp[indexOfLastCoin][amount] = Math.min(use, notUsed);
    
        // System.out.println("dp[" + indexOfLastCoin + "][" + amount + "]:"+dp[indexOfLastCoin][amount]);
        return dp[indexOfLastCoin][amount];
    }
    
    • 分析二迭代求法
    private int INVALID = Integer.MAX_VALUE;
    
    // 分析二 根据amount限制对应的硬币个数,在01背包里面加上一层循环
    public int coinChange(int[] coins, int amount){
        int[][] dp = new int[coins.length+1][amount+1];
    
        for (int i = 0; i < dp.length; i++) {
            Arrays.fill(dp[i], INVALID);
        }
    
        for (int i = 0; i < dp.length; i++) {
            dp[i][0] = 0;
        }
    
        for (int i = 1; i <= coins.length; i++) {
            for (int j = 1; j <= amount; j++) {
                int min = INVALID;
                // 这里k必须从0开始,不一定要用到conis[i-1],可以不用;
                for (int k = 0; k <= j/coins[i-1]; k++) {
                    int leftAmount = j-coins[i-1]*k;
                    if(leftAmount < 0 || dp[i-1][leftAmount]==INVALID)
                        continue;
    
                    min = Math.min(min, dp[i-1][leftAmount] + k);
                }
    
                dp[i][j] = min;
            }
        }
    
        return dp[coins.length][amount] == INVALID ? -1:dp[coins.length][amount];
    }
    
    • 分析二节省空间版本
    private int INVALID = Integer.MAX_VALUE;
    
    // 分析二节省空间版本 根据amount限制对应的硬币个数,在01背包里面加上一层循环
    public int coinChange(int[] coins, int amount){
        int[] dp = new int[amount+1];
    
        Arrays.fill(dp, INVALID);
        dp[0] = 0;
    
        for (int i = 1; i <= coins.length; i++) {
            for (int j = amount; j >= 1; j--) {
                int min = INVALID;
                // 这里k必须从0开始,不一定要用到conis[i-1],可以不用;
                for (int k = 0; k <= j/coins[i-1]; k++) {
                    int leftAmount = j-coins[i-1]*k;
                    if(leftAmount < 0 || dp[leftAmount]==INVALID)
                        continue;
    
                    min = Math.min(min, dp[leftAmount] + k);
                }
    
                dp[j] = min;
            }
        }
    
        return dp[amount] == INVALID ? -1:dp[amount];
    }
    
    • 分析二递归求法(DFS剪枝版本,每个硬币使用的次数是有上限的,超时!!)
    private int INVALID = Integer.MAX_VALUE;
    
    // 分析二递归版本 根据amount限制对应的硬币个数,在01背包里面加上一层循环
    public int coinChange(int[] coins, int amount){
        int result = coinChange(coins, amount, 0, 0);
        return result == INVALID?-1:result;
    }
    
    private int coinChange(int[] coins, int amount, int coinIndex, int coinUsed) {
        if(amount==0)
            return coinUsed;
    
        if(coinIndex==coins.length)
            return INVALID;
    
        int min = INVALID;
    
        for (int i = 0; i <= amount / coins[coinIndex]; i++) {
            min = Math.min(min, coinChange(coins, amount - coins[coinIndex] * i, coinIndex + 1, coinUsed+i));
        }
    
        return min;
    }
    
    • 其他DFS剪枝版本
        // DFS 剪枝
        private int min = Integer.MAX_VALUE;
    
        public int coinChange(int[] coins, int amount) {
            if(amount==0)
                return 0;
    
            int[] sortedCoins = Arrays.stream(coins).boxed().sorted(Comparator.reverseOrder()).mapToInt(Integer::intValue).toArray();
    
            coinChange(sortedCoins, 0, amount, 0);
            return min==Integer.MAX_VALUE?-1:min;
        }
    
        private void coinChange(int[] coins, int start, int amount, int usedCoins) {
            if(amount<0) {
                return ;
            }
    
            if(amount==0) {
                min = Math.min(usedCoins, min);
            }
    
            for (int i = start; i < coins.length; i++) {
                // min 代表题目给的amount目前找到的最小的硬币的个数
                // min - usedCoins 代表还可以使用最多的个数
                // coins[i]是最大的,所以coins[i]*(min - usedCoins)都凑不够,不用试了。
                if(min!= Integer.MAX_VALUE && (coins[i]*(min - usedCoins))<amount)
                    return ;
    
                coinChange(coins, i, amount-coins[i], usedCoins + 1);
            }
        }
    
    • BFS
        // BFS 从某个点是amount开始,coin是边
        public int coinChange(int[] coins, int amount) {
            if (amount == 0)
                return 0;
    
            Deque<Integer> queue = new LinkedList<>();
            Set<Integer> set = new HashSet<>();
            queue.add(0);
            set.add(0);
            int numCoins = 0;
    
            while(queue.size()!=0){
                int size = queue.size();
                numCoins += 1;
    
                for (int i = 0; i < size; i++) {
                    int element = queue.removeFirst();
                    set.remove(element);
                    for (int coin : coins) {
                        int newElement = element + coin;
                        if(newElement==amount) {
                            return numCoins;
                        }else if(newElement < amount) {
                            if(!set.contains(newElement)) {
                                queue.addLast(newElement);
                                set.add(newElement);
                            }
                        }
                    }
                }
            }
    
            return -1;
        }
    

    相关文章

      网友评论

          本文标题:背包系列问题——换零钱

          本文链接:https://www.haomeiwen.com/subject/rsxgdktx.html