J'essaie d'implémenter un problème de pièce de monnaie
Créez une fonction pour compter toutes les combinaisons possibles de pièces pouvant être utilisées pour un montant donné.
All possible combinations for given amount=15, coin types=1 6 7
1) 1,1,1,1,1,1,1,1,1,1,1,1,1,1,1,
2) 1,1,1,1,1,1,1,1,1,6,
3) 1,1,1,1,1,1,1,1,7,
4) 1,1,1,6,6,
5) 1,1,6,7,
6) 1,7,7,
prototype de fonction:
int findCombinationsCount(int amount, int coins[])
supposons que le tableau de pièces est trié. pour l'exemple ci-dessus, cette fonction doit renvoyer 6.
Quelqu'un me guide comment mettre en œuvre cela?
Vous pouvez utiliser des méthodes de fonction génératrice pour donner des algorithmes rapides, qui utilisent des nombres complexes.
Étant donné les valeurs de pièce c1, c2, .., ck, pour obtenir le nombre de façons de faire la somme n, il vous faut le coefficient de x ^ n dans
(1 + x^c1 + x^(2c1) + x^(3c1) + ...)(1+x^c2 + x^(2c2) + x^(3c2) + ...)....(1+x^ck + x^(2ck) + x^(3ck) + ...)
Ce qui revient à trouver le coefficient de x ^ n dans
1/(1-x^c1) * 1/(1-x^c2) * ... * (1-x^ck)
En utilisant maintenant des nombres complexes, x ^ a - 1 = (x-w1) (x-w2) ... (x-wa) où w1, w2, etc. sont les racines complexes de l’unité.
Alors
1/(1-x^c1) * 1/(1-x^c2) * ... * (1-x^ck)
peut être écrit comme
1/(x-a1)(x-a2)....(x-am)
qui peuvent être réécrites en utilisant des fractions partielles
A1/(x-a1) + A2/(x-a2) + ... + Am/(x-am)
Le coefficient de x ^ n dans ceci peut être facilement trouvé:
A1/(a1)^(n+1) + A2/(a2)^(n+1) + ...+ Am/(am)^(n+1).
Un programme informatique devrait facilement pouvoir trouver Ai et ai (qui pourraient être des nombres complexes). Bien sûr, cela pourrait impliquer des calculs en virgule flottante.
Pour n grand, ce sera probablement plus rapide que d'énumérer toutes les combinaisons possibles.
J'espère que cela pourra aider.
Utilisez la récursivité.
int findCombinationsCount(int amount, int coins[]) {
return findCombinationsCount(amount, coins, 0);
}
int findCombinationsCount(int amount, int coins[], int checkFromIndex) {
if (amount == 0)
return 1;
else if (amount < 0 || coins.length == checkFromIndex)
return 0;
else {
int withFirstCoin = findCombinationsCount(amount-coins[checkFromIndex], coins, checkFromIndex);
int withoutFirstCoin = findCombinationsCount(amount, coins, checkFromIndex+1);
return withFirstCoin + withoutFirstCoin;
}
}
Vous devriez cependant vérifier cette implémentation. Je n'ai pas de Java IDE ici et je suis un peu rouillé. Il peut donc y avoir des erreurs.
Bien que la récursivité puisse fonctionner et soit souvent une tâche à implémenter dans certains cours de niveau collégial sur les algorithmes et les structures de données, je pense que la mise en œuvre de "programmation dynamique" est plus efficace.
public static int findCombinationsCount(int sum, int vals[]) {
if (sum < 0) {
return 0;
}
if (vals == null || vals.length == 0) {
return 0;
}
int dp[] = new int[sum + 1];
dp[0] = 1;
for (int i = 0; i < vals.length; ++i) {
for (int j = vals[i]; j <= sum; ++j) {
dp[j] += dp[j - vals[i]];
}
}
return dp[sum];
}
Très simple avec la récursion:
def countChange(money: Int, coins: List[Int]): Int = {
def reduce(money: Int, coins: List[Int], accCounter: Int): Int = {
if(money == 0) accCounter + 1
else if(money < 0 || coins.isEmpty) accCounter
else reduce(money - coins.head, coins, accCounter) + reduce(money, coins.tail, accCounter)
}
if(money <= 0 || coins.isEmpty) 0
else reduce(money, coins, 0)
}
Ceci est un exemple dans SCALA
La réponse d’Aryabhatta pour Compter le nombre de façons de rendre la monnaie avec des pièces de monnaie ayant une dénomination fixe Plutôt que d'utiliser des nombres complexes, nous utiliserons l'arithmétique Modulaire, similaire à la façon dont la transformation de la théorie des nombres remplace une transformation .ier. Pour transformer des polynômes d'entiers.
Soit D
le plus petit commun multiple des dénominations monétaires. Selon le théorème de Dirichlet sur les progressions arithmétiques, il existe infiniment De nombreux nombres premiers p
tels que D
divise p - 1
. (Avec un peu de chance, Ils seront même distribués de manière à pouvoir les trouver Efficacement.) Nous calculerons le nombre de façons dont modulo une p
Satisfaisant cette condition. En obtenant une limite brute d'une manière ou d'une autre (par exemple, n + k - 1
choisissez k - 1
où n
est le total et k
est le nombre De dénominations), répétez cette procédure avec plusieurs nombres premiers Dont le produit dépasse cette limite, et appliquez En Chine, le théorème des restes, nous pouvons récupérer le nombre exact.
Testez les candidats 1 + k*D
pour les entiers k > 0
jusqu'à ce que nous trouvions une prime p
. Soit g
une racine primitive modulo p
(génère des candidats à Aléatoire et applique le test standard). Pour chaque valeur nominale d
, exprimez Le polynôme x**d - 1
modulo p
en tant que produit de facteurs:
x**d - 1 = product from i=0 to d-1 of (x - g**((p-1)*i/d)) [modulo p].
Notez que d
divise D
divise p-1
, l’exposant est donc un entier
Soit m
la somme des dénominations. Rassemblez toutes les constantes g**((p-1)*i/d)
sous la forme a(0), ..., a(m-1)
. La prochaine étape consiste à trouver une décomposition en fraction Partielle A(0), ..., A(m-1)
telle que
sign / product from j=0 to m-1 of (a(j) - x) =
sum from j=0 to m-1 of A(j)/(a(j) - x) [modulo p],
où sign
est 1
s'il existe un nombre pair de dénominations et -1
s'il existe un nombre impair de dénominations. Dérivez un système d'équations linéaires Pour A(j)
en évaluant les deux côtés de l'équation donnée Pour différentes valeurs de x
, puis résolvez-le avec une élimination de. Gaussian La vie se complique s'il y a des doublons; il est probablement plus facile de choisir un autre nombre premier.
Compte tenu de cette configuration, nous pouvons calculer le nombre de façons (modulo p
, of Course) d’apporter des modifications équivalant à n
comme
sum from j=0 to m-1 of A(j) * (1/a(j))**(n+1).
package algorithms;
import Java.util.Random;
/**`enter code here`
* Owner : Ghodrat Naderi
* E-Mail: [email protected]
* Date : 10/12/12
* Time : 4:50 PM
* IDE : IntelliJ IDEA 11
*/
public class CoinProblem
{
public static void main(String[] args)
{
int[] coins = {1, 3, 5, 10, 20, 50, 100, 200, 500};
int amount = new Random().nextInt(10000);
int coinsCount = 0;
System.out.println("amount = " + amount);
int[] numberOfCoins = findNumberOfCoins(coins, amount);
for (int i = 0; i < numberOfCoins.length; i++)
{
if (numberOfCoins[i] > 0)
{
System.out.println("coins= " + coins[i] + " Count=" + numberOfCoins[i] + "\n");
coinsCount += numberOfCoins[i];
}
}
System.out.println("numberOfCoins = " + coinsCount);
}
private static int[] findNumberOfCoins(int[] coins, int amount)
{
int c = coins.length;
int[] numberOfCoins = new int[coins.length];
while (amount > 0)
{
c--;
if (amount >= coins[c])
{
int quotient = amount / coins[c];
amount = amount - coins[c] * quotient;
numberOfCoins[c] = quotient;
}
}
return numberOfCoins;
}
}
Les solutions récursives mentionnées fonctionneront, mais elles seront terriblement lentes si vous ajoutez plus de dénominations de pièces et/ou augmentez la valeur cible de manière significative.
Pour accélérer le processus, vous devez mettre en œuvre une solution de programmation dynamique. Jetez un coup d'œil au problème knapsack . Vous pouvez adapter la solution DP décrite ici pour résoudre votre problème en conservant le nombre de possibilités d’atteindre un total plutôt que le nombre minimum de pièces requis.
Une solution récursive pourrait être la bonne réponse ici:
int findCombinationsCount(int amount, int coins[])
{
// I am assuming amount >= 0, coins.length > 0 and all elements of coins > 0.
if (coins.length == 1)
{
return amount % coins[0] == 0 ? 1 : 0;
}
else
{
int total = 0;
int[] subCoins = arrayOfCoinsExceptTheFirstOne(coins);
for (int i = 0 ; i * coins[0] <= amount ; ++i)
{
total += findCombinationsCount(amount - i * coins[0], subCoins);
}
return total;
}
}
Attention: je n'ai pas testé ni même compilé ce qui précède.
La solution fournie par @Jordi est agréable mais fonctionne extrêmement lentement. Vous pouvez essayer d’entrer 600 dans cette solution et voir à quel point elle est lente.
Mon idée est d'utiliser la programmation dynamique ascendante.
Notez que généralement, la combinaison possible pour money = m et les pièces {a, b, c} est égale à la combinaison pour
Si aucune pièce n'est disponible ou si les pièces disponibles ne peuvent pas couvrir le montant d'argent requis, il convient de saisir 0 dans le bloc en conséquence. Si le montant est égal à 0, il convient de renseigner 1.
public static void main(String[] args){
int[] coins = new int[]{1,2,3,4,5};
int money = 600;
int[][] recorder = new int[money+1][coins.length];
for(int k=0;k<coins.length;k++){
recorder[0][k] = 1;
}
for(int i=1;i<=money;i++){
//System.out.println("working on money="+i);
int with = 0;
int without = 0;
for(int coin_index=0;coin_index<coins.length;coin_index++){
//System.out.println("working on coin until "+coins[coin_index]);
if(i-coins[coin_index]<0){
with = 0;
}else{
with = recorder[i-coins[coin_index]][coin_index];
}
//System.out.println("with="+with);
if(coin_index-1<0){
without = 0;
}else{
without = recorder[i][coin_index-1];
}
//System.out.println("without="+without);
//System.out.println("result="+(without+with));
recorder[i][coin_index] = with+without;
}
}
System.out.print(recorder[money][coins.length-1]);
}
Ce code est basé sur la solution fournie par JeremyP qui fonctionne parfaitement et que je viens de perfectionner pour optimiser les performances en utilisant une programmation dynamique.
public static long makeChange(int[] coins, int money) {
Long[][] resultMap = new Long[coins.length][money+1];
return getChange(coins,money,0,resultMap);
}
public static long getChange(int[] coins, int money, int index,Long[][] resultMap) {
if (index == coins.length -1) // if we are at the end
return money%coins[index]==0? 1:0;
else{
//System.out.printf("Checking index %d and money %d ",index,money);
Long storedResult =resultMap[index][money];
if(storedResult != null)
return storedResult;
long total=0;
for(int coff=0; coff * coins[index] <=money; coff ++){
total += getChange(coins, money - coff*coins[index],index +1,resultMap);
}
resultMap[index][money] = total;
return total;
}
}
Encore une fois, en utilisant récursivité une solution testée, bien que probablement pas le code le plus élégant. (Notez qu’il renvoie le nombre de chaque pièce à utiliser plutôt que de répéter le montant réel n fois).
public class CoinPerm {
@Test
public void QuickTest() throws Exception
{
int ammount = 15;
int coins[] = {1,6,7};
ArrayList<solution> solutionList = SolvePerms(ammount, coins);
for (solution sol : solutionList)
{
System.out.println(sol);
}
assertTrue("Wrong number of solutions " + solutionList.size(),solutionList.size() == 6);
}
public ArrayList<solution> SolvePerms(int ammount, int coins[]) throws Exception
{
ArrayList<solution> solutionList = new ArrayList<solution>();
ArrayList<Integer> emptyList = new ArrayList<Integer>();
solution CurrentSolution = new solution(emptyList);
GetPerms(ammount, coins, CurrentSolution, solutionList);
return solutionList;
}
private void GetPerms(int ammount, int coins[], solution CurrentSolution, ArrayList<solution> mSolutions) throws Exception
{
int currentCoin = coins[0];
if (currentCoin <= 0)
{
throw new Exception("Cant cope with negative or zero ammounts");
}
if (coins.length == 1)
{
if (ammount % currentCoin == 0)
{
CurrentSolution.add(ammount/currentCoin);
mSolutions.add(CurrentSolution);
}
return;
}
// work out list with one less coin.
int coinsDepth = coins.length;
int reducedCoins[] = new int[(coinsDepth -1 )];
for (int j = 0; j < coinsDepth - 1;j++)
{
reducedCoins[j] = coins[j+1];
}
// integer rounding okay;
int numberOfPerms = ammount / currentCoin;
for (int j = 0; j <= numberOfPerms; j++)
{
solution newSolution = CurrentSolution.clone();
newSolution.add(j);
GetPerms(ammount - j * currentCoin,reducedCoins, newSolution, mSolutions );
}
}
private class solution
{
ArrayList<Integer> mNumberOfCoins;
solution(ArrayList<Integer> anumberOfCoins)
{
mNumberOfCoins = anumberOfCoins;
}
@Override
public String toString() {
if (mNumberOfCoins != null && mNumberOfCoins.size() > 0)
{
String retval = mNumberOfCoins.get(0).toString();
for (int i = 1; i< mNumberOfCoins.size();i++)
{
retval += ","+mNumberOfCoins.get(i).toString();
}
return retval;
}
else
{
return "";
}
}
@Override
protected solution clone()
{
return new solution((ArrayList<Integer>) mNumberOfCoins.clone());
}
public void add(int i) {
mNumberOfCoins.add(i);
}
}
}
Vous trouverez ci-dessous la récursion avec la solution Java de mémorisation. pour moins d'un, nous avons 1,2,3,5 comme pièces et 200 comme montant cible.
countCombinations(200,new int[]{5,2,3,1} , 0, 0,new Integer[6][200+5]);
static int countCombinations(Integer targetAmount, int[] V,int currentAmount, int coin, Integer[][] memory){
//Comment below if block if you want to see the perf difference
if(memory[coin][currentAmount] != null){
return memory[coin][currentAmount];
}
if(currentAmount > targetAmount){
memory[coin][currentAmount] = 0;
return 0;
}
if(currentAmount == targetAmount){
return 1;
}
int count = 0;
for(int selectedCoin : V){
if(selectedCoin >= coin){
count += countCombinations(targetAmount, V, currentAmount+selectedCoin, selectedCoin,memory);
}
}
memory[coin][currentAmount] = count;
return count;
}
Première idée:
int combinations = 0;
for (int i = 0; i * 7 <=15; i++) {
for (int j = 0; j * 6 + i * 7 <= 15; j++) {
combinations++;
}
}
(le '<=' est superflu dans ce cas, mais est nécessaire pour une solution plus générale, si vous décidez de modifier vos paramètres)