Уменьшить сумму разностей между соседними элементами массива

Я столкнулся с проблемой кодирования в Интернете, вопрос указан ниже:

Попросите функцию FoodDistribution(arr) прочитать массив чисел. хранится в arr, который будет представлять уровень голода различных люди в диапазоне от 0 до 5 (0 означает, что они совсем не голодны, 5 означает, что очень голоден). У вас также будет N бутербродов, которые вы сможете раздать. диапазон от 1 до 20. Формат массива будет [N, h1, h2, h3, ...] где N представляет количество сэндвичей, которые у вас есть, а остальное части массива будут представлять уровень голода разных людей. Ваша цель — минимизировать разницу в голоде между каждой парой люди в ряду используют имеющиеся у вас бутерброды.

Например: если arr равно [5, 3, 1, 2, 1], это означает, что у вас есть 5 бутерброды раздать. Вы можете распределить их в следующем порядке народу: 2, 0, 1, 0. Дать людям эти бутерброды уровни голода теперь стали: [1, 1, 1, 1]. Разница между каждым пара людей теперь равна 0, сумма также равна 0, поэтому ваша программа должна return 0. Примечание. Возможно, вам не придется раскрывать все или даже некоторые из ваших сэндвичи, чтобы минимизировать разницу.

Другой пример: если arr равно [4, 5, 2, 3, 1, 0], то вы можете распределить бутерброды в следующем порядке: [3, 0, 1, 0, 0], что делает все уровни голода следующие: [2, 2, 2, 1, 0]. Различия между каждой парой людей теперь: 0, 0, 1, 1 и так ваша программа должен вернуть окончательную минимальную разницу 2.

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

  1. Цикл, пока бутерброды не станут равны нулю
  2. Для каждого элемента массива скопируйте массив и удалите один голод в позиции i.
  3. Получите лучшую комбинацию, которая даст вам наименьшую разницу в голоде.
  4. Уменьшите количество сэндвичей на один и считайте локальный минимум новым массивом голода.
  5. Повторяйте до тех пор, пока количество бутербродов не станет равным нулю или разница в голоде не станет равна нулю.

Я думал, что взятие локального минимума приводит к глобальному минимуму, что было неправильно, исходя из следующего варианта использования [7, 5, 4, 3, 4, 5, 2, 3, 1, 4, 5]

def FoodDistribution(arr):
    sandwiches = arr[0]
    hunger_levels = arr[1:]

    # Function to calculate the total difference
    def total_difference(hunger_levels):
        return sum(abs(hunger_levels[i] - hunger_levels[i + 1]) for i in range(len(hunger_levels) - 1))

    def reduce_combs(combs):
        local_min = float('inf')
        local_min_comb = None
        for comb in combs:
            current_difference = total_difference(comb)
            if current_difference < local_min:
                local_min = current_difference
                local_min_comb = comb

        return local_min_comb
    # Function to distribute sandwiches
    def distribute_sandwiches(sandwiches, hunger_levels):
        global_min = total_difference(hunger_levels)
        print(global_min)
        while sandwiches > 0 and global_min > 0:
            combs = []
            for i in range(len(hunger_levels)):
                comb = hunger_levels[:]
                comb[i] -= 1
                combs.append(comb)

            local_min_comb = reduce_combs(combs)
            x = total_difference(local_min_comb)
            print( sandwiches, x, local_min_comb)
            global_min = min(global_min, x)
            hunger_levels = local_min_comb
            sandwiches -= 1
        return global_min

    # Distribute sandwiches and calculate the minimized difference
    global_min = distribute_sandwiches(sandwiches, hunger_levels)
    return global_min

if __name__ == "__main__":
    print(FoodDistribution([7, 5, 4, 3, 4, 5, 2, 3, 1, 4, 5]))

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

  1. Рекурсия до тех пор, пока выход за пределы поля или сэндвичи не станут равны нулю.
  2. Для каждой локации есть два варианта: использовать бутерброд или игнорировать
  3. Если есть возможность использовать сэндвич, уменьшите сэндвичи на единицу и останьтесь с тем же индексом.
  4. Если опция игнорируется, увеличьте индекс на единицу.
  5. Возьмите минимум между двумя вариантами и верните его.

Проблема здесь в том, что я не знал, что хранить в заметке, а хранения индекса и сэндвичей недостаточно. Я не уверен, что эта задача имеет большую сложность, чем 2^(n+s). Есть ли способ узнать, не является ли динамическое программирование или запоминание способом решения проблемы, и в этом случае можно ли повысить сложность за счет запоминания или эту проблему необходимо решить с помощью другого подхода?

def FoodDistribution(arr):
    sandwiches = arr[0]
    hunger_levels = arr[1:]

    # Distribute sandwiches and calculate the minimized difference
    global_min = solve(0, sandwiches, hunger_levels)
    return global_min


def solve(index, sandwiches, hunger_levels):
    if index >= len(hunger_levels) or sandwiches == 0:
        return total_difference(hunger_levels)

    # take a sandwich
    hunger_levels[index] += -1
    sandwiches += -1
    minTake = solve(index, sandwiches, hunger_levels)
    hunger_levels[index] += 1
    sandwiches += 1

    # dont take sandwich
    dontTake = solve(index + 1, sandwiches, hunger_levels)

    return min(minTake, dontTake)


def total_difference(hunger_levels):
    return sum(abs(hunger_levels[i] - hunger_levels[i + 1]) for i in range(len(hunger_levels) - 1))

if __name__ == "__main__":
    print(FoodDistribution([7, 5, 4, 3, 4, 5, 2, 3, 1, 4, 5]))

Обновлено: несколько состояний дадут вам оптимальный ответ для приведенного выше варианта использования.

sandwiches = 7 
hunger = [5, 4, 3, 4, 5, 2, 3, 1, 4, 5]
optimal is 6
states as follow
[3, 3, 3, 3, 3, 2, 2, 1, 4, 5]
[4, 3, 3, 3, 3, 2, 2, 1, 4, 4]
[4, 4, 3, 3, 2, 2, 2, 1, 4, 4]
[4, 4, 3, 3, 3, 2, 1, 1, 4, 4]
[4, 4, 3, 3, 3, 2, 2, 1, 3, 4]
[4, 4, 3, 3, 3, 2, 2, 1, 4, 4]
[5, 4, 3, 3, 3, 2, 2, 1, 3, 3]

Примечание. Я принял ответ @Matt Timmermans, поскольку он обеспечивает наилучшую временную сложность n и nlogn. Но два других ответа потрясающие, их легко понять и можно реализовать с помощью динамического программирования или запоминания. Лично я предпочитаю версию с запоминанием. Ожидаемая временная сложность равна snh, где h — максимальный уровень голода в массиве.

@Unmitigated правильный вывод - 6

Mohd Alomar 19.05.2024 17:32

[6, 2, 1, 5, 12, 20, 20, 20, 18, 4, 3], похоже, дают 33 для вашего второго метода и 34 для метода в ответе Мэтта Тиммерманса.

גלעד ברקן 21.05.2024 14:09

Это может быть крайний случай в реализации Мэтта. Я запустил его, используя грубую силу и запоминание, и результат должен быть 33. Выходные данные: [2, 1, 5, 12, 18, 18, 18, 18, 4, 3]

Mohd Alomar 21.05.2024 15:52

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

גלעד ברקן 21.05.2024 16:18
Почему в Python есть оператор "pass"?
Почему в Python есть оператор "pass"?
Оператор pass в Python - это простая концепция, которую могут быстро освоить даже новички без опыта программирования.
Некоторые методы, о которых вы не знали, что они существуют в Python
Некоторые методы, о которых вы не знали, что они существуют в Python
Python - самый известный и самый простой в изучении язык в наши дни. Имея широкий спектр применения в области машинного обучения, Data Science,...
Основы Python Часть I
Основы Python Часть I
Вы когда-нибудь задумывались, почему в программах на Python вы видите приведенный ниже код?
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
LeetCode - 1579. Удаление максимального числа ребер для сохранения полной проходимости графа
Алиса и Боб имеют неориентированный граф из n узлов и трех типов ребер:
Оптимизация кода с помощью тернарного оператора Python
Оптимизация кода с помощью тернарного оператора Python
И последнее, что мы хотели бы показать вам, прежде чем двигаться дальше, это
Советы по эффективной веб-разработке с помощью Python
Советы по эффективной веб-разработке с помощью Python
Как веб-разработчик, Python может стать мощным инструментом для создания эффективных и масштабируемых веб-приложений.
5
4
282
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

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

by position in the array
    by current hunger level (after distributing food)
        by total food left
            smallest sum of differences so far

Этого достаточно, чтобы узнать, какая наименьшая сумма находится в конце массива.

При желании также отслеживайте:

            # of ways to achieve smallest hunger level

Это позволит вам узнать, сколько способов получить эту наименьшую сумму.

При желании также отслеживайте:

            list of previous hunger levels that can achieve it

И теперь вы можете реконструировать все решения, которые этого достигают.

Я оставлю вам возможность сделать это на самом деле. (В моей истории есть много примеров решения подобных задач.)

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

Cary Swoveland 20.05.2024 04:09

Не могли бы вы объяснить второй уровень «по текущему уровню голода (после раздачи еды)?» Я попытался использовать следующий ключ = «{index}-{sandwiches}-{hunger_levels», который, я считаю, уменьшит сложность до s* n*(nPs) это то, что вы подразумеваете под текущим уровнем голода?

Mohd Alomar 20.05.2024 06:39

@MohdAlomar состояние можно выразить как f(i, value, remaining) -> min sum of adjacent differences (значение — «уровень голода»). Тогда у нас есть f(...) = min(f(i - 1, v, r) + abs(value - v)), over the possible values of v and r, where A[i] - value ≥ remaining.

גלעד ברקן 20.05.2024 18:59

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

btilly 20.05.2024 21:53

@MohdAlomar Я имею в виду вот что. Если индекс 9 начинался с уровня голода 4, то, скорее всего, в индексе 9 будут записи для уровней голода 0, 1, 2, 3 и 4, в зависимости от того, раздали ли мы только что 4, 3, 2, 1 или 0 единиц еды. индекс 9. И если одним из способов получить этот минимальный балл был уровень голода 2 с индексом 9, когда осталось 15 еды, то есть решение, прослеживающее назад индекс 8 с оставшимся 17 едой. (Вам нужно будет поискать, сколько там было роздано, если это не было записано...)

btilly 20.05.2024 21:57

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

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

2 sandwiches
2 4 4 3 4 5
          ^ Cost 1 reduces by 1
            but prevents further
            reductions.

  ^^^ Cost 2, reduces by 2.

Я не уверен, существует ли общее решение с полиномиальным временем, потому что в конечном итоге выбор заключается в некотором разделении «холмов», которое нужно уменьшить (имеется в виду как минимум три последовательных элемента, где все средние элементы больше, чем крайний левый и крайний правый; или конец массива секции, если они выше своего соседа). У нас может быть динамическая программа, основанная на индексе, уровне голода и оставшихся бутербродах, как кратко описано, где последнее зависит от диапазона значений.

Ответ принят как подходящий

Сумма абсолютных разностей уменьшается только при уменьшении локального максимума.

Если вы уменьшите максимум на любом конце, сумма разностей уменьшится на единицу, например [3,2,1] -> [2,2,1].

Если вы уменьшите максимум посередине, сумма разностей уменьшится на два, например [1,3,2] -> [1,2,2].

Если максимум уменьшается, он может объединиться с другим максимумом, который вы можете уменьшить, но новый максимум никогда не будет более дешевым или экономически эффективным. Он может быть только шире, например [1,3,2] -> [1,2,2].

Таким образом, оптимальная стратегия состоит в том, чтобы многократно сокращать наиболее экономически эффективный максимум, с точки зрения benefit/width, который у вас есть достаточное количество бутербродов, чтобы сократить его. benefit — 1 для максимумов на концах или 2 для максимумов в середине.

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

Вы можете сделать это за время O(n), найдя все максимумы и поместив их в приоритетную очередь, чтобы обрабатывать их в правильном порядке по мере их уменьшения.

O(n log n) — это просто. Чтобы сделать эту привязку O(n), вам нужно будет использовать приоритетную очередь с подсчетом и сортировкой вместо кучи. Вам также нужно быть немного умнее, отслеживая области массива, которые, как известно, находятся на одной высоте, чтобы вы могли объединить их за постоянное время.

Вот реализация O (n) в Python

def distribute(arr):

    foodLeft = arr[0]
    hungers = arr[1:]

    # For each element in hungers, calculate number of adjacent elements at same height
    spans = [1] * len(hungers)
    for i in range(1, len(hungers)):
        if hungers[i-1]==hungers[i]:
            spans[i] = spans[i-1]+1
    for i in range(len(hungers)-2, -1, -1):
        if hungers[i+1]==hungers[i]:
            spans[i] = spans[i+1]

    # spans are identified by their first element.  Only the counts and hungers on the edges need to be correct

    # if a span is a maximum, it's height.  Otherwise 0
    def maxHeight(left):
        ret = len(spans)
        if left > 0:
            ret = min(ret, hungers[left] - hungers[left-1])
        right = left + spans[left]-1
        if right < len(spans)-1:
            ret = min(ret, hungers[right] - hungers[right+1])
        return max(ret,0)
    
    # change the height of a span and return the maybe new span that it is a part of
    def reduce(left, h):
        right = left + spans[left] - 1
        hungers[left] -= h
        hungers[right] = hungers[left]
        if right < len(spans)-1 and hungers[right+1] == hungers[right]:
            # merge on the right
            w = spans[right+1]
            spans[right] = spans[right+1] = 0 # for debuggability
            right += w
        if left > 0 and hungers[left-1] == hungers[left]:
            # merge on left
            w = spans[left-1]
            spans[left] = spans[left-1] = 0 # for debuggability
            left -= w
        spans[left] = spans[right] = right - left + 1
        return left
    
    def isEdge(left):
        return left < 1 or left + spans[left] >= len(spans)
    
    # constant-time priority queue for non-edge spans
    # it's just a list of spans per width
    pq = [[] for _i in range(len(spans)+1)]

    # populate priority queue
    curspan = 0
    while curspan < len(spans):
        width = spans[curspan]
        if maxHeight(curspan) > 0 and not isEdge(curspan):
            pq[width].append(curspan)
        curspan += width

    # this will be True at the end if we can sacrifice one edge max selection to get one
    # mid max selection, which would produce one more point
    canBacktrack = False
    # process mid spans in order
    curpri = 1
    # while not all hungers are the same
    while spans[0] < len(spans):

        # find the best middle maximum
        bestmid = None
        midwidth = None
        if curpri < len(pq) and curpri <= foodLeft:
            if len(pq[curpri]) == 0:
                curpri += 1
                continue
            bestmid = pq[curpri][-1]
            midwidth = spans[bestmid]

        # find the best edge maximum
        bestedge = None
        edgewidth = None
        if maxHeight(0) > 0 and foodLeft >= spans[0]:
            bestedge = 0
            edgewidth = spans[0]
        r = len(spans)-spans[-1]
        if maxHeight(r) > 0 and foodLeft >= spans[r] and (bestedge == None or spans[r] < edgewidth):
            bestedge = r
            edgewidth = spans[r]

        # choose
        bestspan = None
        h = 0
        if bestedge == None:
            if bestmid == None:
                break
            bestspan = bestmid
            bestwidth = midwidth
            canBacktrack = False
        elif bestmid == None:
            bestspan = bestedge
            bestwidth = edgewidth
            canBacktrack = False
        elif midwidth <= edgewidth*2:
            # mid maximum is more cost effective
            # OR choo
            bestspan = bestmid
            bestwidth = midwidth
            canBacktrack = False
        else:
            bestspan = bestedge
            bestwidth = edgewidth
            # tentative
            canBacktrack = True
        
        if bestspan == bestmid:
            # chose the middle span -- remove from pq
            pq[curpri].pop()

        # how much we can reduce this maxium by
        h = min(foodLeft//bestwidth, maxHeight(bestspan))
        foodLeft -= bestwidth*h
        canBacktrack = canBacktrack and foodLeft < midwidth and foodLeft + edgewidth >= midwidth
        bestspan = reduce(bestspan, h)
        if maxHeight(bestspan) > 0 and not isEdge(bestspan):
            pq[spans[bestspan]].append(bestspan)
    
    # finally, calculate the new total diffs
    totaldiff = 0
    curspan = spans[0]
    while curspan < len(spans):
        totaldiff += abs(hungers[curspan] - hungers[curspan-1])
        curspan += spans[curspan]
    if canBacktrack:
        totaldiff -= 1
    return totaldiff

# test
cases = [
    [8, 11, 14, 15, 16, 13, 2, 3],
    [7, 5, 4, 3, 4, 5, 2, 3, 1, 4, 5],
    [2, 4, 4, 3, 4, 5],
    [3, 3, 4, 4, 4, 3, 4],
    [4, 3, 4, 4, 4, 3, 5],
    [5, 3, 4, 4, 4, 3, 6],
    [3, 3, 4, 4, 3, 4, 5]
]
for case in cases:
    print("{0}: {1}".format(case, distribute(case)))

Можете ли вы добавить код, который мы можем протестировать? Кроме того, не могли бы вы рассказать о примере в моем ответе.

גלעד ברקן 20.05.2024 19:50

Я добавил реализацию. В вашем примере не имеет значения, какой выбор вы сделаете, минимальная разница в конце в любом случае равна 2. в 2 3 4 4 3 4 5 это имеет значение. Я добился этой цели, отдав предпочтение максимумам в середине... но здесь все еще может быть проблема. Я подумаю об этом. Если есть проблема, я почти уверен, что она имеет решение с постоянным временем, потому что нам нужно будет сравнить только лучший максимум, смежный с краем, с лучшим средним максимумом.

Matt Timmermans 20.05.2024 21:56

Да, было осложнение. Должно быть исправлено сейчас. Я просто отслеживал, можно ли в конце пожертвовать одним снижением на краю, чтобы получить одно снижение в середине, что принесло бы дополнительный балл.

Matt Timmermans 20.05.2024 22:50

[8, 11, 14, 15, 16, 13, 2, 3], похоже, дают 13 для второго метода ОП и 15 для метода в этом ответе.

גלעד ברקן 21.05.2024 11:35

[6, 2, 1, 5, 12, 20, 20, 20, 18, 4, 3], похоже, дают 33 для второго метода ОП и 34 для метода в недавно отредактированном ответе.

גלעד ברקן 21.05.2024 14:08

Я не большой эксперт, но почему вы не думаете, что это NP-подобно, а скорее возможно выбрать без полного представления окончательного, оптимального раздела?

גלעד ברקן 21.05.2024 14:21

Ну, это, конечно, не NP сложно. Тот факт, что выбираемые промежутки монотонно ухудшаются как по краям, так и в середине, предполагает, что жадная стратегия должна работать. Единственная трудность состоит в том, чтобы выяснить, как ограничить количество вариантов выбора края, чтобы 1 выбор края не мешал 1 выбору посередине из-за того, что у него закончилась еда.

Matt Timmermans 21.05.2024 14:26

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

Matt Timmermans 22.05.2024 03:09

Прохладный! Может просто описать алгоритм?

גלעד ברקן 22.05.2024 03:21

Код динамического программирования в моем ответе показывает, что оптимальные значения для [8, 11, 14, 15, 16, 13, 2, 3] и [6, 2, 1, 5, 12, 20, 20, 20, 18, 4, 3] — это 13 и 33 соответственно.

Cary Swoveland 01.06.2024 02:18

Вот более длинный тестовый пример, для которого оптимальным значением является 70: [30, 2, 9, 8, 7, 2, 11, 10, 11, 0, 2, 6, 4, 8, 0, 9, 4, 8, 7, 1, 8, 1, 0, 3, 4, 10, 6, 9, 4, 11, 6].

Cary Swoveland 02.06.2024 19:10

Вот решение для динамического программирования на Ruby, которое, ввиду сходства Ruby и Python, можно довольно легко перевести на Python. 1 Возможно, именно эту модель динамического программирования имел в виду @btilly в своем ответе.

def opt(nbr_sands, hlevels)
  arr = Array.new(hlevels.size) { Array.new(nbr_sands+1) }
  (0..nbr_sands).each do |sands_left|
    arr[0][sands_left] = hash_person_0(nbr_sands-sands_left, hlevels.first)
  end
  (1..hlevels.size-1).each do |p|
    (0..nbr_sands).each do |sands_left|
      arr[p][sands_left] = hash_person_p(nbr_sands, sands_left, hlevels[p],
        hlevels[p-1], arr[p-1])
    end
  end
  arr
end
def hash_person_0(sands_for_person_0, hlevel)
  val = sands_for_person_0 <= hlevel ? 0 : Float::INFINITY
  { sands_for_person_0 => { val: val, opt_prev_sands: nil } }
end 
def hash_person_p(nbr_sands, sands_left, hlevel, prev_hlevel, prev_arr )
  max_sands_to_p = [nbr_sands - sands_left, hlevel].min
  (0..max_sands_to_p).each_with_object({}) do |sands_to_p, h|
    adj_hlevel = hlevel - sands_to_p
    prev_sands_left = sands_left + sands_to_p
    n, d = prev_arr[prev_sands_left].min_by do |n,d|
      d[:val] + (adj_hlevel - (prev_hlevel - n)).abs
    end
    val = d[:val] + (adj_hlevel - (prev_hlevel - n)).abs 
    h[sands_to_p] = { val: val, opt_prev_sands: n }
  end
end

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


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

Здесь Принцип оптимальности Беллмана требует, чтобы при любой из переменных состояния для человека p оптимальное распределение сэндвичей остальным людям не зависело от распределения сэндвичей лицам, предшествующим человеку p, который дал повышение данному состоянию. переменная для человека p. Это требование явно удовлетворено.


Предполагать

nbr_sands = 2
hlevels = [3, 2, 1]

Очевидно, что существуют два оптимальных распределения:

[1, 1, 1]

и

[1, 0, 0]

То есть лица 0 и 1 должны получить 1 сэндвич, а человек 2 не должен получить ни одного (снижение [3, 2, 1] до [2, 1, 1]), или человек 0 должен получить 1 сэндвич, а люди 1 и 2 не должны получить ни одного (сокращение [3, 2, 1] до [2, 2, 1]), что дает оптимальные значения 1 в обоих случаях.


Для этих значений nbr_sands и hlevels

opt(nbr_sands, hlevels)

возвращает следующий (аннотированный) массив arr, где arr[p][n] соответствует хэшу, связанному с человеком p (основание ноль), когда сэндвичи n остаются после всех распределений между людьми до человека p включительно. Я объясняю значение этих хэшей ниже.

[
  # p = 0 #
  [
    {2=>{:val=>0, :opt_prev_sands=>nil}},
    {1=>{:val=>0, :opt_prev_sands=>nil}}, # * and **
    {0=>{:val=>0, :opt_prev_sands=>nil}}
  ],
  # p = 1
  [
    # sands_left = 0
    {
      0=>{:val=>1, :opt_prev_sands=>2},
      1=>{:val=>1, :opt_prev_sands=>1}, # * prev sands left = 0 + 1 = 1, prev sands = 1
      2=>{:val=>3, :opt_prev_sands=>0}
    },
    # sands_left = 1
    {
      0=>{:val=>0, :opt_prev_sands=>1}, # * prev sands left = 1 + 0 = 1, prev sands = 1
      1=>{:val=>2, :opt_prev_sands=>0}
    },
    # sands_left = 2
    {
      0=>{:val=>1, :opt_prev_sands=>0}
    }
  ],
  # p = 2
  [
    # sands_left = 0
    {
      0=>{:val=>1, :opt_prev_sands=>1}, # * prev sans_left = 0 + 0 = 0, prev sands = 1
      1=>{:val=>2, :opt_prev_sands=>0}
    },
    # sands_left = 1
    {
      0=>{:val=>1, :opt_prev_sands=>0}, # ** prev sans_left = 1 + 0 = 1, prev sands = 0
      1=>{:val=>3, :opt_prev_sands=>0}
    },
    # sands_left = 2
    {
      0=>{:val=>2, :opt_prev_sands=>0}
    }
  ]
]

Теперь мы можем найти оптимальное решение, начиная с конца, с человеком p = 2.

Нас интересует, какая комбинация sandwiches left и присвоения человеку 2 минимизирует значение :val в связанном хэше.

Их два, обозначены * и **. Обычно мы заинтересованы в поиске оптимального решения только в том случае, если их несколько. Итак, давайте посмотрим на файл, отмеченный *. Это соответствует

arr[2][0][0]
  #=> {:val=>1, :opt_prev_sands=>1}

Первый [0] соответствует тому, что осталось ноль бутербродов; последний [0] возвращает значение хеша, когда человеку 2 выдается ноль бутербродов. Это означает, что количество бутербродов в начале периода 2 и, следовательно, в конце периода 1 равно 0 + 0 #=> 0, а оптимальное количество бутербродов, данное человеку 1, равно 1.

Это означает, что нам далее необходимо изучить

arr[1][0][1]
  # {:val=>1, :opt_prev_sands=>1}

Это означает, что количество сэндвичей, оставшихся после распределения человеку 1, равно 0 + 1 = 1 и человек 0 должен получить 1 сэндвич. Таким образом, одно оптимальное решение даёт [1, 1, 0], снижая уровень голода до [3-1, 2-1, 1-0] #=> [2, 1, 1].

Я оставлю читателю упражнение по вычислению второго оптимального решения.

1. I am in the process of teaching myself Python and as part of the endeavior will in time edit my answer to give the equivalent solution in Python.

Другие вопросы по теме