Twoim zadaniem jest rozwiązanie problemu SLCSC, polegającego na znalezieniu najkrótszego możliwego kodu do rozwiązania problemu najdłuższej wspólnej kolejności .
Prawidłowe rozwiązanie problemu LCS przez dwa lub więcej ciągów S 1 , S ... n jest dowolny ciąg T maksymalnej długości tak, że znaki T pojawiają się we wszystkich S I , w tej samej kolejności jak w T .
Zauważmy, że T nie musi być sub ciąg od S í .
Przykład
Ciągi znaków axbycz
i xaybzc
mają 8 wspólnych podciągów o długości 3:
abc abz ayc ayz xbc xbz xyc xyz
Każde z nich stanowiłoby prawidłowe rozwiązanie problemu LCS.
Detale
Napisz program lub funkcję, która rozwiąże problem LCS, jak wyjaśniono powyżej, przestrzegając następujących zasad:
Dane wejściowe będą składać się z dwóch lub więcej ciągów zawierających tylko małe litery.
Możesz odczytać te ciągi jako tablicę ciągów lub pojedynczy ciąg z dowolnym ogranicznikiem.
Twój kod musi wypisać dowolne z możliwych rozwiązań problemu, opcjonalnie poprzedzone wierszem wiersza lub otoczone cudzysłowami.
Możesz założyć, że łańcuchy są krótsze niż 1000 znaków i że istnieje maksymalnie 20 łańcuchów.
W tych granicach Twój kod powinien działać zgodnie z oczekiwaniami teoretycznymi (biorąc pod uwagę nieograniczony czas i pamięć).
Twój kod musi wypełnić połączone przypadki testowe następnej sekcji w ciągu godziny na moim komputerze (Intel Core i7-3770, 16 GiB RAM).
Podejścia, które po prostu powtarzają wszystkie możliwe podsekwencje, nie będą zgodne z terminem.
Używanie wbudowanych, które trywializują to zadanie, takich jak np.
LongestCommonSequence
, Jest niedozwolone.
Obowiązują standardowe zasady gry w golfa .
Przypadki testowe
a
ab
Wynik: a
aaaaxbbbb
bbbbxcccc
ccccxaaaa
Wynik: x
hxbecqibzpyqhosszypghkdddykjfjcajnwfmtfhqcpavqrtoipocijrmqpgzoufkjkyurczxuhkcpehbhpsuieqgjcepuhbpronmlrcgvipvibhuyqndbjbrrzpqbdegmqgjliclcgahxoouqxqpujsyfwbdthteidvigudsuoznykkzskspjufgkhaxorbrdvgodlb
qnnprxqpnafnhekcxljyysobbpyhynvolgtrntqtjpxpchqwgtkpxxvmwwcohxplsailheuzhkbtayvmxnttycdkbdvryjkfhshulptkuarqwuidrnjsydftsyhuueebnrjvkfvhqmyrclehcwethsqzcyfvyohzskvgttggndmdvdgollryqoswviqurrqhiqrqtyrl
Wyjście: hxbbpyhogntqppcqgkxchpsieuhbncvpuqndbjqmclchqyfttdvgoysuhrrl
lub jakikolwiek inny podsekwencja o tej samej długości
riikscwpvsbxrvkpymvbbpmwclizxlhihiubycxmxwviuajdzoonjpkgiuiulbjdpkztsqznhbjhymwzkutmkkkhirryabricvhb
jnrzutfnbqhbaueicsvltalvqoorafnadevojjcsnlftoskhntashydksoheydbeuwlswdzivxnvcrxbgxmquvdnfairsppodznm
kzimnldhqklxyezcuyjaiasaeslicegmnwfavllanoolkhvqkjdvxrsjapqqwnrplcwqginwinktxnkfcuuvoyntrqwwucarfvjg
Wyjście: icsvllvjnlktywuar
lub jakikolwiek inny podsekwencja o tej samej długości
rblartqkfggrjpiipuzzypkycnyckkcqceeujiyy
yfpnedyjtiwrhyuktripdwyvgkblzodeufkelhub
ywcyboxwqkibwvredkrbdsyudkulpvmhixeqxynh
bnxwahbzhwjxkotnvbxrojkkldtsodtjmvdrmbgr
Wyjście: krkk
lub jakikolwiek inny podsekwencja o tej samej długości
bwfscmotshoczmduwaev
coywaaizdaxjovipsmeh
dobzcpoiedenzlfdjpiu
bbhfeqscvvbwkuoxdoge
drqrzwbcpcsvneodelja
Wyjście: code
lub jakikolwiek inny podsekwencja o tej samej długości
nqrualgoedlf
jgqorzglfnpa
fgttvnogldfx
pgostsulyfug
sgnhoyjlnfvr
wdttgkolfkbt
Wyjście: golf
lub jakikolwiek inny podsekwencja o tej samej długości
epopyfuhgowedpiqpgfj
ddxyestqynpwmytxhozm
ptubgzyqqksieoovuezv
tovotqmnhgzttfpywjgr
aomvytkgaijlgqzkljls
vzvxpaixrnprxvenbbuo
syfuwxlpcyontqlmfvib
arxleuomstkjegpduwcx
xgqrxaopouvkvwgbzewn
yggunddigctgpnuztzai
izroomywwztdymqszsuo
kiawjnxjncdtufhkrjsp
Dane wyjściowe: pusty ciąg
źródło
Odpowiedzi:
CJam, 31
Wypróbuj online
9 bajtów dzięki Dennisowi!
Wyjaśnienie:
Algorytm ten wypróbowuje każdy możliwy znak dla pierwszej pozycji podsekwencji, zastępuje każdy ciąg podłańcuchem po pierwszym wystąpieniu tego znaku, a następnie wywołuje się rekurencyjnie (z zapamiętywaniem).
źródło
Python -
665644Poziomy wcięć:
Kod definiuje funkcję
o
, która przyjmuje listę ciągów znaków jako argumenty i zwraca jeden z LCS dla tych ciągów.Kod testowy:
Czas potrzebny na uruchomienie testów na moim komputerze:
źródło
(n+1)
, możesz go zastąpić,-~n
aby zaoszczędzić 2 bajty w każdym przypadku. Poza tym, gdziekolwiek używaszmap
zlambda
, rozważ zamiast tego użycie listy. Na przykładmap(lambda x:x-1,z)
można skrócić o trzy bajty, zmieniając go na[~-x for x in z]
.r,x=r+(j+1)*x,x*(l[k]+1)
można skrócić dor+=(j+1)*x;x*=(l[k]+1)
. Ponadto nie potrzebujesz,u=...
ponieważu
jest używany tylko w jednym miejscu. Po prostu zastąp ten kod literąu
.Pyth,
59585535 bajtówWytnij aż 20 bajtów dzięki @isaacg!
Wersja 55-bajtowa:
Odetnij 3 bajty, zmieniając
.U@bZ
na@F
(operator składania).Wersja 58-bajtowa:
Odetnij bajt, zmieniając format warunku logicznego.
Wersja 59-bajtowa:
To było trudne! Python ciągle się segreguje! Byłem całkiem pewien, że to jakiś błąd, ale nie udało mi się uzyskać minimalnego przypadku testowego. No cóż.
Ten algorytm oparłem na tym . Co jest w porządku, z tym wyjątkiem, że ten jest przeznaczony tylko dla dwóch łańcuchów. Musiałem go trochę ulepszyć, żeby działał dalej. Potem ostatni przypadek testowy trwał zbyt długo, więc musiałem dodać dodatkową kontrolę, aby wyjść z rekurencji, jeśli nie ma więcej typowych znaków.
Jest to dość powolne, ale powinno zająć mniej niż godzinę (miejmy nadzieję). Testuję na moim Core i3 z 6 GB pamięci RAM, więc Twój 16-GB Core i7 powinien przez to przejść. :)
Skorzystałem również z funkcji automatycznego zapamiętywania Pytha, aby trochę przyspieszyć.
EDYCJA : @Dennis powiedział, że mija!
Aby to przetestować, dodaj następujący wiersz:
i podaj mu listę ciągów poprzez standardowe wejście (np
['a', 'ab']
.).Objaśnienie dla wersji 35-bajtowej:
WIP
Objaśnienie dotyczące wersji 55-bajtowej:
źródło
C,
618564 bajtówI tutaj jest wyjaśnione, dla „czytelności”:
Panie i panowie! Popełniłem straszny błąd. Jest używany do być ładniejsza ... I goto mniej ... Przynajmniej teraz jest szybka .
Definiujemy funkcję rekurencyjną,
L
która przyjmuje jako dane wejściowe tablicęs
tablic znaków i liczbęn
łańcuchów. Funkcja wyświetla wynikowy ciąg znaków na standardowe wyjście i przypadkowo zwraca rozmiar znaków tego ciągu.Podejście
Chociaż kod jest zawiły, strategia tutaj nie jest zbyt skomplikowana. Zaczynamy od dość naiwnego algorytmu rekurencyjnego, który opiszę za pomocą pseudokodu:
Ten algorytm sam w sobie jest dość okropny (znalazłem, że można go zmieścić w około ~ 230 bajtach). Nie w ten sposób uzyskuje się szybkie wyniki. Ten algorytm skaluje się niezwykle słabo przy długości łańcucha. Algorytm ten jest jednak dość dobrze skalować z większej liczby łańcuchów. Ostatni przypadek testowy zostałby rozwiązany praktycznie natychmiast, ponieważ żadne ciągi znaków nie
s
mając
wspólnych znaków . Zaimplementowałem dwie główne sztuczki, które spowodowały niesamowity wzrost prędkości:Przy każdym wezwaniu do
L
sprawdź, czy otrzymaliśmy już tę samą informację. Ponieważ w praktyce informacja jest przekazywana dookoła poprzez wskaźniki do tego samego zestawu strun, nie faktycznie trzeba porównać ciągi, tylko miejsca, które jest świetne. Jeśli okaże się, że już otrzymaliśmy te informacje, nie trzeba przeprowadzać obliczeń (przez większość czasu, ale uzyskanie danych wyjściowych sprawia, że jest to trochę bardziej skomplikowane) i możemy uciec od samego powrotu długości. Jeśli nie znajdziemy dopasowania, zapisz ten zestaw danych wejściowych / wyjściowych, aby porównać z przyszłymi połączeniami. W kodzie C drugafor
pętla próbuje znaleźć dopasowania do danych wejściowych. Znane wskaźniki wejściowe są zapisywaneR
, a odpowiadające im wartości długości i znaków są przechowywane wA
. Ten plan miał drastyczny wpływ na środowisko wykonawcze, szczególnie w przypadku dłuższych łańcuchów.Za każdym razem znaleźć lokalizacje
c
ws
, istnieje szansa, wiemy, tuż nietoperza, że to, co odkryliśmy, nie jest optymalna. Jeśli każda lokalizacjac
pojawi się po znanej lokalizacji innej litery, automatycznie wiemy, żec
nie prowadzi to do optymalnego podciągu, ponieważ można w niej zmieścić jeszcze jedną literę. Oznacza to, że za niewielką opłatą możemy potencjalnie usunąć kilkaset wezwań doL
dużych ciągów. W powyższym kodzie Cy
jest zestaw flag, jeśli automatycznie wiemy, że ten znak prowadzi do nieoptymalnego ciągu, iz
jest zestawem flag, jeśli znajdziemy znak, który ma wyłącznie wcześniejszy wygląd niż jakikolwiek inny znany znak. Obecne najwcześniejsze pojawienia się znaków są przechowywane wx
. Obecna implementacja tego pomysłu jest nieco niechlujna, ale w wielu przypadkach niemal podwojona wydajność.Dzięki tym dwóm pomysłom, co nie zakończyło się w ciągu godziny, zajęło teraz około 0,015 sekundy.
Prawdopodobnie istnieje wiele innych małych sztuczek, które mogą przyspieszyć wydajność, ale w tym momencie zacząłem martwić się o moją umiejętność gry w golfa. Wciąż nie jestem zadowolony z golfa, więc prawdopodobnie wrócę do tego później!
Czasy
Oto kod testowy, który zapraszam do wypróbowania online :
Testy OP przeprowadziłem na laptopie wyposażonym w procesor Intel Core i7 1,7 GHz z ustawieniem optymalizacji na
-Ofast
. Symulacja wykazała, że wymagany szczyt to 712 KB. Oto przykładowy przebieg każdego przypadku testowego z czasami:Podczas gry w golfa dość znacząco poprawiłem wydajność, a ponieważ ludzie wydawali się lubić brutalną prędkość (0,013624 s, aby ukończyć wszystkie przypadki testowe łącznie) mojego poprzedniego 618-bajtowego rozwiązania, zostawię to tutaj w celach informacyjnych:
Sam algorytm pozostaje niezmieniony, ale nowy kod opiera się na podziałach i trudniejszych operacjach bitowych, które ostatecznie spowalniają cały proces.
źródło
best_found
? Jest wspomniany tylko dwa razy, raz, gdy jest używany w warunkowym, a drugi, gdy jest resetowany.best_found
jest ustawione nabest_length + current_depth
. Powinieneś chyba wspomnieć o tym w wyjaśnieniu!best_found
to globalna liczba całkowita, która opisuje długość najdłuższego wspólnego podłańcucha znalezionego w dowolnym punkcie wykonania. Umieszczę to w wyjaśnieniu!Python 2, 285
Kod:
Stosowanie:
Wyjaśnienie:
To jest funkcja rekurencyjna.
s
to postać, której szukamy.a
zawiera listę ciągów pokrojonych pos
.b
zawiera listę ciągów jeszcze nietkniętych.f
zwraca najdłuższy wspólny ciąg.Pierwszy warunek sprawdza, czy skończyliśmy przechodzić przez wszystkie łańcuchy. jeśli tak, oznacza to, że
s
jest pospolitym charakterem i powraca ws
poszukiwaniu bardziej powszechnych znaków.Drugi warunek sprawdza, czy nie zaczynamy przechodzić przez dowolny ciąg znaków, co oznacza, że nie mamy nawet znaku (
a==[]
jest równoważnys==''
). jeśli tak, sprawdzamy każdy znak pierwszego ciągu wb
.Ostatni wiersz przenosi pierwszy ciąg znaków
b
doa
, znajdując każde wystąpienies
tego ciągu.Przy pierwszym wywołaniu
s
powinien być pusty ciąg.a
powinna być pusta lista ib
powinna zawierać wszystkie ciągi.źródło
f(b,s='',a=[])
.