简体   繁体   中英

calculating the number of ways of representing n cents

Working on the algorithm puzzle of calculating the number of ways of representing n cents, using unlimited number of 25 cents, 10 cents, 5 cents and 1 cents. Referred a few different solutions and have a question about below solution, I am not sure why we are confident when if (index >= denoms.length - 1) there must be a solution, and there must be a unique solution? Is it a special optimization just because we have cent = 1 special value, or a more general optimization approach? Appreciate for more insights.

import java.util.Arrays;

public class Question { 
    public static int makeChange(int amount, int[] denoms, int index) {
        if (index >= denoms.length - 1) return 1; // one denom remaining -> one way to do it
        int denomAmount = denoms[index];
        int ways = 0;
        for (int i = 0; i * denomAmount <= amount; i++) {
            int amountRemaining = amount - i * denomAmount;
            ways += makeChange(amountRemaining, denoms, index + 1); // go to next denom
        }
        return ways;
    }

    public static int makeChange1(int n) {
        int[] denoms = {25, 10, 5, 1};
        return makeChange(n, denoms, 0);
    }

    public static int makeChange2(int n) {
        int[] denoms = {25, 10, 5, 1};
        int[][] map = new int[n + 1][denoms.length];
        return makeChange2(n, denoms, 0, map);
    }

    public static int makeChange2(int amount, int[] denoms, int index, int[][] map) {
        if (map[amount][index] > 0) { // retrieve value
            return map[amount][index];
        }
        if (index >= denoms.length - 1) return 1; // one denom remaining -> one way to do it
        int denomAmount = denoms[index];
        int ways = 0;
        for (int i = 0; i * denomAmount <= amount; i++) {
            // go to next denom, assuming i coins of denomAmount
            int amountRemaining = amount - i * denomAmount;
            ways += makeChange2(amountRemaining, denoms, index + 1, map);
        }
        map[amount][index] = ways;
        return ways;
    }   

    public static int makeChange(int n) {
        int x = makeChange1(n);
        int y = makeChange2(n);
        if (x != y) {
            System.out.println("Error: " + x + " " + y);
        }
        return x;
    }

    public static void main(String[] args) {
        for (int i = 0; i <= 100; i++) {
            System.out.println("makeChange(" + i + ") = " + makeChange(i));
        }
    }

}

If it doesn't return when index > denoms.length - 1 , then denoms[index] will give ArrayIndexOutOfBoundsException .

So question now is why it will return 1 when index == denoms.length - 1 ?

The reason is quite simple, becasue the last denom is just 1 cent!

Any amountRemaining will have just one way to do with 1 cent.

So, if you change your array to {25, 10, 5, 2} , you will find your solution broken down(when it comes to {25, 10, 5, 2} , makeChange(3) will return 1 , but you see, it should be 0 ).

If you want to fix your solution when the last denom is not 1 cent, you should just change if (index >= denoms.length - 1) return 1 to:

if (index > denoms.length - 1)
    return 0;
if (index == denoms.length - 1) {
    // one denom remaining
    if (amount % denoms[denoms.length - 1] == 0) {
        return 1;
    }
    else {
        return 0;
    }
}

Then with {25, 10, 5, 2} :

makeChange(1) = 0
makeChange(2) = 1
makeChange(3) = 0

EDIT:

Is it a special optimization just because we have cent = 1 special value, or a more general optimization approach?

You can call it an optimisation, but as my code gives, you can do it with any cent = x , just by amount % denoms[denoms.length - 1] == 0 .

If I'm not mistaken, this is very much a coin changing problem .

I assume that you understand the problem, since that your question is very specific towards that line if (index >= denoms.length - 1) return 1; , so I will skip the explanation.

Interestingly enough, I ran your whole code in Eclipse using smallest denominations of 1, 2, 3 and 4 cents [IE 25/10/5/1, OR 25/10/5/2, OR 25/10/5/3, OR 25/10/5/4]. In every one of the runs, I got the exact same solution (these represent the smallest sub-problems). So why is this the case? Wouldn't it make sense that if I had (for example) 3 cents, the solution of 25/10/5/4 should be 0 since I can't take a single coin (given that the smallest denomination is greater than the amount of cents required? However, what if you think of the 1 way it returns as the way that there is no solution found? That is, if the 1 return represents the empty set of coins where coins taken do not hit the required amount (or no coins are taken)?

Taking 6 cents in the question (n = 6), with denominations 25/10/5/4, we obtain the solutions of 5 + nothing[Invalid], and 4 + nothing[Invalid]. Doing the same if the smallest denomination is 3, we get 5 + nothing[Invalid], and 3+3. Repeating for 2 cents smallest, we get 5 + nothing[Invalid], and 2 + 2 + 2. Repeating for 1 cent smallest, we get 5 + 1 and 1 + 1 ... + 1. The total for every solution ends up to be 2, despite the invalid cases!

This is because the solution which you provided assumes the invalid ways (but with coins taken, and an empty set when the value of n is larger than the smallest denomination) are counted as one way each. That's why even for when n = 10 (with 2 being the smallest denomination), we get 4 ways (10, 5*2, 2*5 and 5 + 2*2 [Invalid]). Do note that the invalid cases are "optimal solutions", meaning that these are the "best" invalid cases which are linked to each denomination.

I do believe that the solution provided by Sayakiss works towards eliminating this problem of invalid ways being counted (with the special case of n = 0 returning 1 way, which is to take no numbers).

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM