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

  • Входные данные: Целое число .
  • Выходные данные: Минимальное количество монет номиналами , , , чтобы выдать сдачу .
  • Ограничения: .

Пример 1

Ввод

Вывод

2

2

  • .

Пример 2

Ввод

Вывод

28

6

  • Ограничение по времени (с): 1 секунда.
  • Ограничение по памяти: 512 Mb.

Решение

Рассмотрим основную идею решения. Пока сдача положительна , мы выбираем монету с самым большим номиналом, не превышающем , отнимаем значение номинала выбранной монеты от и увеличиваем количество монет:

Change(money):
    numCoins = 0
    while money>0:
        if money >= 10:
            money = money − 10
        else if money >= 5:
            money = money − 5
        else:
            money = money − 1
        numCoins = numCoins + 1
    return numCoins

Также эту задачу можно решить в одну строку:

return floor(money/10) + floor((money mod 10)/5) + (money mod 5)

Проектировать жадные алгоритмы просто, но вот доказывать их правильность — нередко сложная задача. И возможно, вас интересует, почему мы тратим время, чтобы доказать работоспособность очевидного алгоритма Change. Дождитесь, пока мы попадем в алгоритмическую ловушку, и она убедит вас, что доказательство ниже — не трата времени.

Чтобы доказать, что этот жадный алгоритм работает правильно, мы покажем, что выбор монеты с самым большим номиналом соответствует некому оптимальному решению.

То есть нам нужно доказать, что для каждого положительного целого числа существует оптимальный способ выдать сдачу с , который использует как минимум одну монету с номиналом , где — самое большое число из , не превышающее . Чтобы доказать это, мы рассмотрим несколько примеров. В каждом из примеров мы выбираем оптимальное решение (то есть конкретную сдачу с ) и преобразовываем его так, что количество монет не увеличивается и содержит как минимум одну монету с номиналом . Мы также получаем способ выдать сдачу с , который содержит монету , если начинаем с подхода к сдаче .

  • . В этом случае , и единственный способ выдать сдачу с — это использовать монет номиналом 1.
  • . В таком случае . Безусловно, любая сдача с money будет состоять только из монет с номиналами 1 и 5. Если в неё не входит монета с номиналом 5, то входят как минимум пять монет номиналом 1 (так как ). Заменив их на одну монету номиналом 5, мы улучшим это решение.
  • . В таком случае . Рассмотрим способ выдать сдачу с и предположим, что в нём не используется монета номиналом 10. Простое, но важное замечание: сумма некой подгруппы использованных монет — 10. Это можно продемонстрировать, рассмотрев количество монет номиналом 5 в данном решении: если таких монет нет, тогда есть как минимум десять монет номиналом 1, и мы заменяем их на одну 10; если есть лишь одна монета номиналом 5, тогда есть как минимум пять монет по 1, и мы снова заменяем все монеты на одну монету номиналом 10; если есть хотя бы две монеты по 5, тогда их снова можно заменить.

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

✒️ Упражнение:
Продемонстрируйте, что money mod 5 монет номиналом 1 необходимы для любого решения, а остальные следует заменить монетами номиналами 10 и максимум одной монетой номиналом 5.

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

Отмечайте параграфы как прочитанные чтобы видеть свой прогресс обучения

Вступайте в сообщество хендбука

Здесь можно найти единомышленников, экспертов и просто интересных собеседников. А ещё — получить помощь или поделиться знаниями.
Вступить
Сообщить об ошибке
Предыдущий параграф5.2. Вычисление НОК и НОД
Следующий параграф6.2. Задача «Специи»