Я застрял в проблеме деноминации монет.
Я пытаюсь найти самое низкое количество монет, используемых для составления $ 5,70 (или 570 центов). Например, если массив монет {100,5,2,5,1} (100 x 10c монеты, 5 x 20c, 2 x 50c, 5 x $ 1 и 1 x $ 2 монеты), тогда результат должен быть { 0,1,1,3,1} В настоящий момент массив монет будет состоять из одинаковых наименований ($ 2, $ 1, 50c, 20c, 10c)
public static int[] makeChange(int change, int[] coins) {
// while you have coins of that denomination left and the total
// remaining amount exceeds that denomination, take a coin of that
// denomination (i.e add it to your result array, subtract it from the
// number of available coins, and update the total remainder). –
for(int i= 0; i< coins.length; i++){
while (coins[i] > 0) {
if (coins[i] > 0 & change - 200 >= 0) {
coins[4] = coins[4]--;
change = change - 200;
} else
if (coins[i] > 0 & change - 100 >= 0) {
coins[3] = coins[3]--;
change = change - 100;
} else
if (coins[i] > 0 & change - 50 >= 0) {
coins[2] = coins[2]--;
change = change - 50;
} else
if (coins[i] > 0 & change - 20 >= 0) {
coins[1] = coins[1]--;
change = change - 20;
} else
if (coins[i] > 0 & change - 10 >= 0) {
coins[0] = coins[0]--;
change = change - 10;
}
}
}
return coins;
}
Я зациклился на том, как вычесть значения из массива монет и вернуть их.
EDIT: новый код
Ссылка wikipedia неясна в деталях о том, как решить, будет ли жадный алгоритм, такой как ваш, работать. Лучшая ссылка связана в этом вопросе CS StackExchange. По сути, если система монет каноническая, жадный алгоритм обеспечит оптимальное решение. Итак, [1, 2, 5, 10, 20] канонический? (используя 10 центов для единиц, так что последовательность начинается с 1)
Согласно этой статье, 5-монетная система неканоническая тогда и только тогда, когда она удовлетворяет точно одному из следующих условий:
Поэтому, поскольку жадный алгоритм не дает оптимальных ответов (и даже если бы он это сделал, я сомневаюсь, что он будет работать с ограниченными монетами), вам следует попробовать динамическое программирование или некоторое просвещенное обратное отслеживание:
import java.util.HashSet;
import java.util.PriorityQueue;
public class Main {
public static class Answer implements Comparable<Answer> {
public static final int coins[] = {1, 2, 5, 10, 20};
private int availableCoins[] = new int[coins.length];
private int totalAvailable;
private int totalRemaining;
private int coinsUsed;
public Answer(int availableCoins[], int totalRemaining) {
for (int i=0; i<coins.length; i++) {
this.availableCoins[i] = availableCoins[i];
totalAvailable += coins[i] * availableCoins[i];
}
this.totalRemaining = totalRemaining;
}
public boolean hasCoin(int coinIndex) {
return availableCoins[coinIndex] > 0;
}
public boolean isPossibleBest(Answer oldBest) {
boolean r = totalRemaining >= 0
&& totalAvailable >= totalRemaining
&& (oldBest == null || oldBest.coinsUsed > coinsUsed);
return r;
}
public boolean isAnswer() {
return totalRemaining == 0;
}
public Answer useCoin(int coinIndex) {
Answer a = new Answer(availableCoins, totalRemaining - coins[coinIndex]);
a.availableCoins[coinIndex]--;
a.totalAvailable = totalAvailable - coins[coinIndex];
a.coinsUsed = coinsUsed+1;
return a;
}
public int getCoinsUsed() {
return coinsUsed;
}
@Override
public String toString() {
StringBuilder sb = new StringBuilder("{");
for (int c : availableCoins) sb.append(c + ",");
sb.setCharAt(sb.length()-1, '}');
return sb.toString();
}
// try to be greedy first
@Override
public int compareTo(Answer a) {
int r = totalRemaining - a.totalRemaining;
return (r==0) ? coinsUsed - a.coinsUsed : r;
}
}
// returns an minimal set of coins to solve
public static int makeChange(int change, int[] availableCoins) {
PriorityQueue<Answer> queue = new PriorityQueue<Answer>();
queue.add(new Answer(availableCoins, change));
HashSet<String> known = new HashSet<String>();
Answer best = null;
int expansions = 0;
while ( ! queue.isEmpty()) {
Answer current = queue.remove();
expansions ++;
String s = current.toString();
if (current.isPossibleBest(best) && ! known.contains(s)) {
known.add(s);
if (current.isAnswer()) {
best = current;
} else {
for (int i=0; i<Answer.coins.length; i++) {
if (current.hasCoin(i)) {
queue.add(current.useCoin(i));
}
}
}
}
}
// debug
System.out.println("After " + expansions + " expansions");
return (best != null) ? best.getCoinsUsed() : -1;
}
public static void main(String[] args) {
for (int i=0; i<100; i++) {
System.out.println("Solving for " + i + ":"
+ makeChange(i, new int[]{100,5,2,5,1}));
}
}
}
Решение грубой силы состоит в том, чтобы опробовать доступное количество монет с наивысшим наименованием (прекращение, когда вы закончите, или сумма станет отрицательной), и для каждого из этих рекурсоров при решении оставшейся суммы с более коротким списком, который исключает эту деноминацию, и выберите минимум из них. Если базовый случай равен 1c, проблема всегда может быть решена, а базовый случай - return n
иначе n/d0
(d0
представляющий наименьшее деноминацию), но необходимо позаботиться о том, чтобы вернуть большое значение, когда оно равномерно делится, поэтому оптимизация может выбрать другую ветку. Воспоминание возможно и параметризуется оставшейся суммой и следующим наименованием. Таким образом, размер таблицы заметок будет равен O(n*d)
, где n
- начальное количество, а d
- количество наименований.
Таким образом, проблема может быть решена в псевдополиномиальном времени.
Вы находитесь в неправильном направлении. Эта программа не даст вам оптимального решения. Чтобы получить оптимальное решение, выполните динамические алгоритмы, реализованные и обсуждаемые здесь. Посетите эти несколько ссылок: