これは、Thijserの現在不完全な擬似コードに沿ったものです。アイデアは、それがちょうど取られたのでない限り、残りのアイテムタイプの中で最も頻繁に取られることです。(このアルゴリズムのCoadyの実装も参照してください。)
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の2つのアイテムタイプの場合、最適解には、k1 <k2の場合はk2-k1-1欠陥、k1 = k2の場合は0欠陥、k1> k2の場合はk1-k2-1欠陥があります。=の場合は明らかです。その他は対称です。少数派要素の各インスタンスは、可能な合計k1 + k2-1のうち最大2つの欠陥を防ぎます。
この欲張りアルゴリズムは、次のロジックによって最適解を返します。接頭辞(部分解)が最適解にまで及ぶ場合、それを安全と呼びます。明らかに、空のプレフィックスは安全であり、安全なプレフィックスがソリューション全体である場合、そのソリューションが最適です。貪欲な各ステップが安全性を維持していることを帰納的に示すだけで十分です。
貪欲なステップで欠陥が発生する唯一の方法は、アイテムタイプが1つだけ残っている場合です。この場合、続行する方法は1つだけであり、その方法は安全です。それ以外の場合は、検討中のステップの直前の(安全な)プレフィックスをPとし、直後のプレフィックスをP 'とし、Pを拡張する最適解をSとします。SがP'も拡張する場合は、これで完了です。それ以外の場合は、P '= PxおよびS = PQおよびQ = yQ'とします。ここで、xおよびyはアイテムであり、QおよびQ 'はシーケンスです。
まず、Pがyで終わっていないとします。アルゴリズムの選択により、xは少なくともQではyと同じ頻度です。xとyのみを含むQの最大部分文字列を考えてみましょう。最初の部分文字列に少なくともyと同じ数のxがある場合、xで始まる追加の欠陥を導入することなく書き換えることができます。最初の部分文字列のyがxよりも多い場合、他の部分文字列のxがyよりも多いため、追加の欠陥なしにこれらの部分文字列を書き換えて、xが最初になるようにすることができます。どちらの場合も、必要に応じて、P 'を拡張する最適解Tを見つけます。
ここで、Pがyで終わると仮定します。xの最初のオカレンスを前に移動してQを変更します。そうすることで、最大で1つの欠陥(xがあった場所)を導入し、1つの欠陥(yy)を排除します。
すべてのソリューションの生成
これはtobias_kの答えに加えて、現在検討中の選択が何らかの方法でグローバルに制約されている場合を検出するための効率的なテストです。生成のオーバーヘッドは出力の長さのオーダーであるため、漸近的な実行時間が最適です。残念ながら、最悪の場合の遅延は2次式です。より良いデータ構造で線形(最適)に減らすことができます。
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]
あなたの基準とまったく同じですか?