Це відповідає нині неповному псевдокоду Тізера. Ідея полягає в тому, щоб взяти найпоширеніший з решти типів предметів, якщо тільки він не був взятий. (Див. Також реалізацію цього алгоритму Коаді.)
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. Якщо в першому підрядку є принаймні стільки знаків х, скільки у, тоді його можна переписати, не вносячи додаткових дефектів, починаючи з х. Якщо в першому підрядку більше y, ніж x, то в якомусь іншому підрядку більше x, ніж y, і ми можемо переписати ці підрядки без додаткових дефектів, щоб x йшов першим. В обох випадках ми знаходимо оптимальне рішення Т, що розширює Р ', за потреби.
Нехай тепер 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]
за вашим критерієм?