Это похоже на неполный псевдокод Тайсера. Идея состоит в том, чтобы взять наиболее частый из оставшихся типов предметов, если он не был просто взят. (См. Также реализацию этого алгоритма Коуди .)
import collections
import heapq
class Sentinel:
pass
def david_eisenstat(lst):
counts = collections.Counter(lst)
heap = [(-count, key) for key, count in counts.items()]
heapq.heapify(heap)
output = []
last = Sentinel()
while heap:
minuscount1, key1 = heapq.heappop(heap)
if key1 != last or not heap:
last = key1
minuscount1 += 1
else:
minuscount2, key2 = heapq.heappop(heap)
last = key2
minuscount2 += 1
if minuscount2 != 0:
heapq.heappush(heap, (minuscount2, key2))
output.append(last)
if minuscount1 != 0:
heapq.heappush(heap, (minuscount1, key1))
return output
Доказательство правильности
Для двух типов элементов, с количеством k1 и k2, оптимальное решение имеет k2 - k1 - 1 дефектов, если k1 <k2, 0 дефектов, если k1 = k2, и k1 - k2 - 1 дефектов, если k1> k2. Случай = очевиден. Остальные симметричны; каждый случай неосновного элемента предотвращает не более двух дефектов из общего числа возможных k1 + k2 - 1.
Этот жадный алгоритм возвращает оптимальные решения по следующей логике. Мы называем префикс (частичное решение) безопасным, если он продолжается до оптимального решения. Очевидно, что пустой префикс безопасен, и если безопасный префикс представляет собой целое решение, то это решение является оптимальным. Достаточно индуктивно показать, что каждый жадный шаг обеспечивает безопасность.
Единственный способ, которым жадный шаг привнесет дефект, - это если останется только один тип элемента, и в этом случае есть только один способ продолжить, и этот способ безопасен. В противном случае пусть P будет (безопасным) префиксом непосредственно перед рассматриваемым шагом, пусть P 'будет префиксом сразу после, и пусть S будет оптимальным решением, расширяющим P. Если S также расширяет P', тогда мы закончили. В противном случае пусть P '= Px, S = PQ и Q = yQ', где x и y - элементы, а Q и Q '- последовательности.
Предположим сначала, что P не заканчивается на y. По выбору алгоритма x встречается в Q не реже, чем y. Рассмотрим максимальные подстроки Q, содержащие только x и y. Если первая подстрока имеет по крайней мере столько же x, сколько y, то ее можно переписать, не вводя дополнительных дефектов, чтобы начать с x. Если в первой подстроке y больше, чем x, то в какой-то другой подстроке x больше, чем y, и мы можем переписать эти подстроки без дополнительных дефектов, чтобы x шел первым. В обоих случаях мы находим оптимальное решение T, расширяющее P 'по мере необходимости.
Предположим теперь, что P заканчивается на y. Измените Q, переместив первое вхождение x на передний план. При этом мы вводим не более одного дефекта (где раньше был x) и устраняем один дефект (yy).
Создание всех решений
Это ответ tobias_k плюс эффективные тесты для определения того, когда рассматриваемый в настоящее время выбор каким-то образом глобально ограничен. Асимптотическое время выполнения является оптимальным, поскольку накладные расходы на генерацию порядка длины вывода. К сожалению, задержка в худшем случае квадратична; его можно свести к линейному (оптимальному) с лучшими структурами данных.
from collections import Counter
from itertools import permutations
from operator import itemgetter
from random import randrange
def get_mode(count):
return max(count.items(), key=itemgetter(1))[0]
def enum2(prefix, x, count, total, mode):
prefix.append(x)
count_x = count[x]
if count_x == 1:
del count[x]
else:
count[x] = count_x - 1
yield from enum1(prefix, count, total - 1, mode)
count[x] = count_x
del prefix[-1]
def enum1(prefix, count, total, mode):
if total == 0:
yield tuple(prefix)
return
if count[mode] * 2 - 1 >= total and [mode] != prefix[-1:]:
yield from enum2(prefix, mode, count, total, mode)
else:
defect_okay = not prefix or count[prefix[-1]] * 2 > total
mode = get_mode(count)
for x in list(count.keys()):
if defect_okay or [x] != prefix[-1:]:
yield from enum2(prefix, x, count, total, mode)
def enum(seq):
count = Counter(seq)
if count:
yield from enum1([], count, sum(count.values()), get_mode(count))
else:
yield ()
def defects(lst):
return sum(lst[i - 1] == lst[i] for i in range(1, len(lst)))
def test(lst):
perms = set(permutations(lst))
opt = min(map(defects, perms))
slow = {perm for perm in perms if defects(perm) == opt}
fast = set(enum(lst))
print(lst, fast, slow)
assert slow == fast
for r in range(10000):
test([randrange(3) for i in range(randrange(6))])
[1, 2, 1, 3, 1, 4, 1, 5]
точно такое же как[1, 3, 1, 2, 1, 4, 1, 5]
по вашему критерию?