Mój przyjaciel otrzymał dziś podczas wywiadu następujące pytanie na stanowisko programisty:
Biorąc pod uwagę dwa ciągi s1
i s2
jak sprawdzisz, czy s1
jest to obrócona wersja s2
?
Przykład:
Jeśli s1 = "stackoverflow"
to, oto niektóre z jego obróconych wersji:
"tackoverflows"
"ackoverflowst"
"overflowstack"
gdzie jak "stackoverflwo"
to nie obrócony wersja.
Odpowiedź, którą udzielił, brzmiała:
Weź
s2
i znajdź najdłuższy prefiks, który jest podrzędnym ciągiems1
, który da ci punkt obrotu. Po znalezieniu tego punktu, przerwas2
w tym momencie, aby uzyskaćs2a
as2b
, a potem po prostu sprawdzić, czyconcatenate(s2a,s2b) == s1
To wygląda na dobre rozwiązanie dla mnie i mojego przyjaciela. Ale ankieter pomyślał inaczej. Poprosił o prostsze rozwiązanie. Pomóż mi, mówiąc, jak byś to zrobił Java/C/C++
?
Z góry dziękuję.
Odpowiedzi:
Najpierw upewnij się,
s1
is2
są tej samej długości. Następnie sprawdź, czys2
jest podciągs1
połączony zs1
:W Javie:
źródło
(s1+s1).contains(s2)
w Javie.s1+s1
. Oczywiście wszystkie jego podciągi o rozmiarzes1.length
są obrotamis1
według konstrukcji. Dlatego każdy ciąg wielkości,s1.length
który jest podciągiem,s1+s1
musi być rotacją os1
.Z pewnością lepszą odpowiedzią byłoby: „Cóż, zapytałbym społeczność stackoverflow i prawdopodobnie uzyskałbym co najmniej 4 naprawdę dobre odpowiedzi w ciągu 5 minut”. Mózgi są dobre, ale większą wartość miałbym dla kogoś, kto wie, jak współpracować z innymi, aby znaleźć rozwiązanie.
źródło
Kolejny przykład python (oparty na odpowiedzi):
źródło
s2
raczej o zduplikowaniu niżs1
zbyt ... potem uświadomiłem sobie, że relacja i tak była symetryczna.in
operator nie używa algorytmu O (n)?s1 in s2
jest zoptymalizowany. Zobacz effbot.org/zone/stringlib.htm do opisu algorytmu. Wydaje się, że Google wskazuje, że Java nie ma szybkiego przeszukiwania ciągów (patrz na przykład johannburkard.de/software/stringsearch ), choć wątpię, żeby coś zepsułoby, gdyby go zmienili.Ponieważ inni przedstawili kwadratowe rozwiązanie problemu złożoności w najgorszym przypadku, dodam liniowe (oparte na algorytmie KMP ):
przykład roboczy
źródło
EDYCJA: Przyjęta odpowiedź jest wyraźnie bardziej elegancka i wydajniejsza, jeśli ją zauważysz. Pozostawiłem tę odpowiedź jako to, co zrobiłbym, gdybym nie pomyślał o podwojeniu oryginalnej struny.
Po prostu użyłbym tego brutalnie. Najpierw sprawdź długość, a następnie wypróbuj każde możliwe przesunięcie obrotu. Jeśli żaden z nich się nie powiedzie, zwróć false - jeśli którykolwiek z nich się sprawdzi, natychmiast zwróć true.
Nie ma szczególnej potrzeby konkatenacji - wystarczy użyć wskaźników (C) lub indeksów (Java) i przejść obie strony, po jednym w każdym ciągu - zaczynając od początku jednego ciągu i bieżącego przesunięcia obrotu kandydata w drugim ciągu i zawijając w razie potrzeby . Sprawdź równość znaków w każdym punkcie ciągu. Jeśli dojdziesz do końca pierwszego ciągu, gotowe.
Prawdopodobnie byłoby tak łatwo połączyć - choć prawdopodobnie mniej wydajne, przynajmniej w Javie.
źródło
Oto jeden z wyrażeń regularnych dla zabawy:
Możesz to uprościć, jeśli możesz użyć specjalnego znaku ograniczającego, który nie będzie w żadnym łańcuchu.
Zamiast tego możesz także użyć lookbehind ze skończonym powtarzaniem:
źródło
Zaraz, zaraz ... dlaczego wszyscy są tak podekscytowani
O(n^2)
odpowiedzią? Jestem przekonany, że możemy tu zrobić lepiej. Powyższa odpowiedź obejmujeO(n)
operację wO(n)
pętli (wywołanie substring / indexOf). Nawet z bardziej wydajnym algorytmem wyszukiwania; powiedzmyBoyer-Moore
lubKMP
, najgorszy przypadek wciąż dotyczyO(n^2)
duplikatów.O(n)
Randomizowane odpowiedź jest prosta; weź skrót (jak odcisk palca Rabina), który obsługujeO(1)
przesuwne okno; hash string 1, następnie hash string 2 i przejdź do przesuwania okna dla hash 1 wokół łańcucha i sprawdź, czy funkcje hash nie kolidują.Jeśli wyobrażamy sobie, że najgorszym przypadkiem jest coś takiego jak „skanowanie dwóch nici DNA”, to prawdopodobieństwo kolizji rośnie, a to prawdopodobnie przeradza się w coś takiego
O(n^(1+e))
lub coś (zgaduję tutaj).Wreszcie istnieje deterministyczne
O(nlogn)
rozwiązanie, które ma bardzo dużą stałą na zewnątrz. Zasadniczo chodzi o splot dwóch ciągów. Maksymalna wartość splotu będzie różnicą obrotów (jeśli zostaną obrócone); anO(n)
potwierdza czek. Zaletą jest to, że jeśli istnieją dwie równe wartości maksymalne, oba są również poprawnymi rozwiązaniami. Możesz dokonać splotu za pomocą dwóch FFT i iloczynu i iFFT, więcnlogn + nlogn + n + nlogn + n == O(nlogn)
.Ponieważ nie można uzupełniać zerami i nie można zagwarantować, że ciągi mają długość 2 ^ n, FFT nie będą szybkimi; będą to te powolne,
O(nlogn)
ale o wiele większa stała niż algorytm CT.To powiedziawszy, jestem absolutnie w 100% przekonany, że istnieje tu deterministyczne
O(n)
rozwiązanie, ale cholernie, jeśli go znajdę.źródło
%stringsize
) ma gwarantowany czas liniowy.Pięść, upewnij się, że 2 struny mają tę samą długość. Następnie w C możesz to zrobić za pomocą prostej iteracji wskaźnika.
źródło
Oto
O(n)
i na miejscu algorytm. Używa<
operatora dla elementów ciągów. Oczywiście to nie moje. Wziąłem ją stąd (strona jest po polsku. Natknąłem się na nią kiedyś w przeszłości i teraz nie mogłem znaleźć czegoś takiego po angielsku, więc pokazuję, co mam :)).źródło
Myślę, że lepiej to zrobić w
Java
:W Perlu zrobiłbym:
lub jeszcze lepiej przy użyciu funkcji indeksu zamiast wyrażenia regularnego:
źródło
\Q
w/\Q$string2/
.\Q
cytuje wszelkie znaki specjalne w$string2
. Bez tego.
byłoby traktowane jako obrót dowolnego ciągu 1-znakowego.Nie jestem pewien, czy jest to najbardziej wydajna metoda, ale może być stosunkowo interesująca : transformacja Burrowsa-Wheelera . Zgodnie z artykułem WP wszystkie obroty wejścia dają taką samą moc wyjściową. W zastosowaniach takich jak kompresja nie jest to pożądane, dlatego wskazany jest pierwotny obrót (np. Za pomocą indeksu; zobacz artykuł). Ale dla prostego porównania niezależnego od obrotu brzmi idealnie. Oczywiście niekoniecznie jest to idealna wydajność!
źródło
Weź każdą postać jako amplitudę i wykonaj na niej dyskretną transformatę Fouriera. Jeśli różnią się tylko obrotem, widma częstotliwości będą takie same jak w obrębie błędu zaokrąglania. Oczywiście jest to nieefektywne, chyba że długość jest potęgą 2, więc możesz wykonać FFT :-)
źródło
Nikt jeszcze nie zaproponował podejścia modulo, więc oto jedno:
Wynik:
[EDYCJA: 2010-04-12]
piotr zauważył błąd w moim kodzie powyżej. Występuje błąd, gdy pierwszy znak w ciągu występuje dwa razy lub więcej. Na przykład
stackoverflow
testowanie zowstackoverflow
wynikiem dawało fałsz, kiedy powinno być prawdziwe.Dzięki piotr za wykrycie błędu.
Oto poprawiony kod:
Oto wynik:
Oto podejście lambda:
Oto dane wyjściowe podejścia lambda:
źródło
Ponieważ nikt nie podał rozwiązania w języku C ++. tutaj to:
źródło
Prosta sztuczka rotacji wskaźnika w Operze działa, ale jest wyjątkowo nieefektywna w najgorszym przypadku w czasie działania. Po prostu wyobraź sobie ciąg znaków z wieloma długimi powtarzającymi się seriami znaków, tj .:
„Pętla, aż nastąpi niedopasowanie, a następnie zwiększenie o jeden i spróbuj ponownie” jest okropnym podejściem obliczeniowym.
Aby udowodnić, że możesz zastosować metodę konkatenacji na zwykłym C bez większego wysiłku, oto moje rozwiązanie:
Jest to liniowe w czasie wykonywania, kosztem wykorzystania pamięci O (n) w kosztach ogólnych.
(Zauważ, że implementacja strstr () jest specyficzna dla platformy, ale jeśli jest szczególnie martwa w mózgu, zawsze można ją zastąpić szybszą alternatywą, taką jak algorytm Boyer-Moore)
źródło
strstr()
O (n + m)? Ponadto, jeśli standard (lub cokolwiek innego) nie gwarantuje liniowego czasu działaniastrstr()
, nie można stwierdzić, że cały algorytm ma liniową współzależność czasową.s1SelfConcat
: dopiero od C9x C zezwala na zmienne rozmiary tablic (chociaż GCC pozwoliło na to znacznie dłużej) i będziesz miał problemy z przydzielaniem dużych ciągów na stosie. Yosef Kreinin napisał bardzo zabawny post na blogu o tym problemie. Twoje rozwiązanie wciąż zajmuje kwadratowy czas dzięki Boyer-Moore; chcesz KMP.DO#:
źródło
Podoba mi się odpowiedź, która sprawdza, czy s2 jest podciągiem s1 połączonym z s1.
Chciałem dodać optymalizację, która nie traci elegancji.
Zamiast łączyć łańcuchy możesz użyć widoku złączenia (nie znam innego języka, ale dla C ++ Boost.Range zapewnia takie widoki).
Ponieważ sprawdzenie, czy łańcuch jest podciągiem innego, ma złożoność średnią liniową (złożoność najgorszego przypadku jest kwadratowa), ta optymalizacja powinna poprawić szybkość średnio o współczynnik 2.
źródło
Czysta odpowiedź Java (bez sprawdzania wartości zerowej)
źródło
A teraz coś z zupełnie innej beczki.
Jeśli chcesz naprawdę szybkiej odpowiedzi w ograniczonym kontekście, gdy łańcuchy nie obracają się względem siebie
Uzgodnione, może się nie powieść, ale bardzo szybko można stwierdzić, czy ciągi się nie zgadzają, a jeśli się zgadzają, nadal można użyć innego algorytmu, takiego jak konkatenacja ciągów.
źródło
Innym rozwiązaniem Ruby na podstawie tej odpowiedzi:
źródło
Pisanie w PHP jest bardzo łatwe przy użyciu
strlen
istrpos
funkcji:Nie wiem, co
strpos
używa wewnętrznie, ale jeśli używa KMP, będzie to liniowe w czasie.źródło
Odwróć jeden z ciągów. Weź FFT obu (traktując je jako proste sekwencje liczb całkowitych). Pomnóż wyniki razem punktowo. Przekształć z powrotem za pomocą odwrotnego FFT. Wynik będzie miał jeden pik, jeśli struny będą się obracać względem siebie - pozycja piku wskaże, o ile są one obrócone względem siebie.
źródło
Dlaczego nie coś takiego?
Oczywiście możesz napisać własną funkcję IndexOf (); Nie jestem pewien, czy .NET używa naiwnego czy szybszego sposobu.
Naiwny:
Szybciej:
Edycja: Mogę mieć jakieś problemy off-by-one; nie chcę sprawdzać. ;)
źródło
Zrobiłbym to w Perlu :
źródło
źródło
Dołącz
string1
sięstring2
i używać algorytmu KMP w celu sprawdzenia, czystring2
jest obecny w nowo utworzonym ciągiem. Ponieważ złożoność czasowa KMP jest mniejsza niżsubstr
.źródło