Создание цепочки циклов динамически на основе списка

#java #math #chain

#java #математика #цепочка

Вопрос:

Я занимаюсь Java, и я пытался создать программу для вычисления количества способов деления числа с использованием заданного количества разделителей.

Например:

100 — это число, а разделители равны 50,20,5. Каковы возможные разделения.

Ответ будет:

 Amount of 50 : 0, Amount of 20 : 0, Amount of 10 : 10
Amount of 50 : 0, Amount of 20 : 1, Amount of 10 : 8
Amount of 50 : 0, Amount of 20 : 2, Amount of 10 : 6
Amount of 50 : 0, Amount of 20 : 3, Amount of 10 : 4
Amount of 50 : 0, Amount of 20 : 4, Amount of 10 : 2
Amount of 50 : 1, Amount of 20 : 0, Amount of 10 : 5
Amount of 50 : 1, Amount of 20 : 1, Amount of 10 : 3
Amount of 50 : 1, Amount of 20 : 2, Amount of 10 : 1
Amount of 50 : 2
 

Я написал код, который запрашивает у пользователя сумму и 3 делителя. Теперь я пытаюсь выяснить, есть ли способ динамического создания кода для стольких разделителей, сколько захочет пользователь. Код в некотором смысле очень повторяющийся, и существует определенный шаблон для добавления другого разделителя, но я не могу понять, как реализовать это динамическое изменение в коде.

Первый код, который я придумал для этого, следующий:

 public class Test2 {

    public static void main(String[] args) {

        Scanner scanner = new Scanner(System.in);
        System.out.println("Insert the amount:");
        int amount = scanner.nextInt();
        List<Integer> dividers = new ArrayList<>();
        System.out.println("Insert the first divider:");
        int tempDivider = scanner.nextInt();
        if (!dividers.contains(tempDivider)) {
            dividers.add(tempDivider);
        }

        while (dividers.size()<3) {
                System.out.println("Insert the next divider: ("   (3-dividers.size())   " more to go)");
                tempDivider = scanner.nextInt();
            if (!dividers.contains(tempDivider)) {
                dividers.add(tempDivider);
            }
        }

        dividers.sort(Collections.reverseOrder());
        System.out.print("Dividers are: ");
        System.out.println(dividers);

        int getal1 = dividers.get(0);
        int getal2 = dividers.get(1);
        int getal3 = dividers.get(2);

        int fiftyAmount = amount / getal1;
        int fiftyRemainder = amount % getal1;
        for (int i = 0; i <= fiftyAmount; i  ) {
            int currentFiftyAmount = amount - (getal1 * i);
            int twentyAmount = currentFiftyAmount / getal2;
            int twentyRemainder = currentFiftyAmount % getal2;
            if (twentyAmount == 0) {
                StringBuilder output = new StringBuilder();
                output.append("Amount of "   getal1   " banknotes: "   i);
                if (fiftyRemainder != 0) output.append(", Remainder: "   fiftyRemainder);
                System.out.println(output);
            } else {
                for (int j = 0; j <= twentyAmount; j  ) {
                    int currentTwentyAmount = currentFiftyAmount - (getal2 * j);
                    int tenAmount = currentTwentyAmount / getal3;
                    int tenRemainder = currentTwentyAmount % getal3;
                    if (tenAmount == 0) {
                        StringBuilder output = new StringBuilder();
                        output.append("Amount of "   getal1   " banknotes: "   i   ", Amount of "   getal2   " banknotes: "   j);
                        if (tenRemainder != 0) output.append(", Remainder: "   twentyRemainder);
                    } else {
                        StringBuilder output = new StringBuilder();
                        output.append("Amount of "   getal1   " banknotes: "   i   ", Amount of "   getal2   " banknotes: "   j  
                                ", Amount of "   getal3   " banknotes: "   tenAmount);
                        if (tenRemainder != 0) output.append(", Remainder: "   tenRemainder);
                        System.out.println(output);
                    }
                }
            }
        }
    }
}
 

Я попытался сделать это более абстрактным, чтобы найти способ автоматизировать создание дополнительных разделительных циклов, но я не могу понять это.
Более абстрактная версия, которую я написал, выглядит следующим образом:

 import java.util.*;

public class Main {

    public static void main(String[] args) {

        Scanner scanner = new Scanner(System.in);
        System.out.println("Insert the amount:");
        int amount = scanner.nextInt();
        List<Integer> dividers = new ArrayList<>();
        System.out.println("Insert the first divider:");
        dividers.add(scanner.nextInt());

        int divider;
        while (dividers.size()<2) {
            System.out.println("Insert the next divider: ("   (2-dividers.size())   " more to go)");
            divider = scanner.nextInt();
            if (!dividers.contains(divider)) {
                dividers.add(divider);
            }
        }
        dividers.sort(Collections.reverseOrder());
        System.out.print("Dividers are: ");
        System.out.println(dividers);


        int divided1Amount = amount / dividers.get(0);
        int divided1Remainder = amount % dividers.get(0);
        for (int i = 0; i <= divided1Amount; i  ) {
            int currentDivided1Amount = amount - (dividers.get(0) * i);
            int divided2Amount = currentDivided1Amount / dividers.get(1);
            int divided2Remainder = currentDivided1Amount % dividers.get(1);
            if (divided2Amount == 0) {
                StringBuilder output = new StringBuilder();
                output.append(dividers.get(0)   ":"   i);
                if (divided1Remainder != 0) {
                    output.append(", Remainder: "   divided1Remainder);
                }
                System.out.println(output);
            } else {
                StringBuilder output = new StringBuilder();
                output.append(dividers.get(0)   ":"   i   ","   dividers.get(1)   ":"   divided2Amount);
                if (divided2Remainder != 0) {
                    output.append(", Remainder: "   divided2Remainder);
                }
                System.out.println(output);
            }
        }
    }
}
 

Это также доступно на GitHub: https://github.com/realm1930/rekendin/blob/master/src/Main.java

Может кто-нибудь, пожалуйста, просветите меня. Прошу прощения, если я не совсем ясно описал проблему.

Спасибо

Ответ №1:

Хотя к фиксированному числу разделителей можно хорошо подойти с помощью вложенных циклов, я предлагаю для общего случая записать решение в виде рекурсивной функции.

Эта проблема хорошо подходит для динамического программирования. Под этим я подразумеваю, что проблему можно разбить на более простые подзадачи, и таким образом решение естественным образом реализуется с помощью рекурсии. Например, в вашем примере выражения 100 в виде суммы, кратной 50, 20 и 10, найдено три решения, все из которых используют один 50:

 Amount of 50 : 1, Amount of 20 : 0, Amount of 10 : 5
Amount of 50 : 1, Amount of 20 : 1, Amount of 10 : 3
Amount of 50 : 1, Amount of 20 : 2, Amount of 10 : 1
 

Посмотрите на это как на решение подзадачи поиска способов выражения значения 50 в виде кратных 20 и 10 (то есть 50 равно 20*0 10*5 , 20*1 10*3 и 20*2 10*1 ). Таким образом, вы можете разделить и победить исходную проблему в этом смысле.

Пусть X — число (например, 100) для выражения, и D1, D2, … DN разделители. Вот возможный план:

  • Если есть только один разделитель, N = 1, это просто: есть только ноль или одно решение в зависимости от того, делит ли D1 X.
  • В противном случае возможные решения могут иметь D1 с любым кратным от 0, 1, …, X / D1 . Итак, создайте цикл m1 = 0, 1, …, X / D1 и рекурсивно решите подзадачу, имеющую X’ = X — m1 * D1 и оставшиеся делители D2, …, DN. У этой подзадачи на один делитель меньше, поэтому после достаточного количества рекурсий она сводится к случаю N = 1.

Это решает проблему. Обратите внимание, однако, что полная рекурсия может привести к комбинаторно огромному количеству решаемых подзадач. Поэтому для эффективного решения рекомендуется хранить или «запоминать» решения ранее решенных подзадач в таблице, чтобы работа не повторялась.

Другие мысли:

  • Пусть Q — наибольший общий делитель (GCD) всех делителей {D1, …, DN}. Если X не делится на Q, то решений нет, и в этом случае мы можем полностью пропустить приведенный выше рекурсивный поиск. Например. невозможно выразить X = 103 с разделителями 50, 20 и 10. Этот тест GCD также может быть применен к каждой подзадаче, чтобы некоторые рекурсивные вызовы могли возвращаться раньше.
  • Эта проблема является разновидностью диофантова уравнения, более конкретно, она связана с проблемой монеты Фробениуса и числами Фробениуса. Существует сообщение mathoverflow, в котором обсуждается это.

Комментарии:

1. Спасибо за потрясающие знания!