Longest Repeating Character Replacement
Otrzymujesz ciąg znaków s składający się z wielkich liter alfabetu angielskiego oraz liczbę całkowitą k. Możesz wybrać co najwyżej k pozycji w ciągu s i zmienić literę na każdej z nich na dowolną inną wielką literę.
Zwróć długość najdłuższego podciągu, czyli ciągu kolejnych liter, w którym po wprowadzonych zmianach występuje tylko jedna powtarzająca się litera.
Funkcja
- sstring
- ciąg wielkich liter
- kinteger
- największa liczba liter, które możesz zmienić
- Zwracainteger
- długość najdłuższego podciągu składającego się z powtarzającej się litery, jaki możesz utworzyć
Ograniczenia
1 ≤ s.length ≤ 5 × 104szawiera wyłącznie wielkie litery alfabetu angielskiego.0 ≤ k ≤ s.length
Przykłady
- Wejście
- s = "BAAACAB"k = 1
- Wyjście
- 5
- Wyjaśnienie
- Zmień
CnaA, a indeksy od 1 do 5 będą zawieraćAAAAA. Sześć liter wymagałoby dwóch zmian: indeksy od 0 do 5 zawierająBiC, a indeksy od 1 do 6 zawierająCi ostatnieB.
- Wejście
- s = "AABBBAB"k = 2
- Wyjście
- 6
- Wyjaśnienie
- W ciągu
ABBBAB, o indeksach od 1 do 6, dwie literyAsą jedynymi, które nie są literamiB, więc dwie zmiany dająBBBBBB. Cały ciąg zawiera trzy literyAi cztery literyB, więc potrzeba trzech zmian.
- Wejście
- s = "WXYZ"k = 0
- Wyjście
- 1
- Wyjaśnienie
- Jeśli nie wolno wprowadzać żadnych zmian, odpowiedzią jest najdłuższy ciąg znajdujący się już w ciągu znaków. Każda litera różni się od sąsiednich, więc ten ciąg ma długość jednej litery.
+17 ukrytych testów przy wysłaniu
Pytanie dodatkowe
Co się zmieni, jeśli s może zawierać dowolny znak, a nie tylko 26 wielkich liter?
Podpowiedzi
Otwieraj je po kolei. Każda zdradza trochę więcej.
W przypadku jednego ustalonego podciągu, w jaką literę powinna zmienić się każda inna litera i ile zmian to kosztuje?
Podciąg jest poprawny, gdy jego długość pomniejszona o liczbę wystąpień najczęściej występującej w nim litery wynosi co najwyżej
k. Znajdź najdłuższe okno spełniające tę zasadę, przesuwając dwa końce do przodu wzdłuż ciągu znaków.Przechowuj 26 liczników i największy licznik
top. Dodaj jedną literę po prawej stronie; jeśli teraz okno wymaga więcej niżkzmian, usuń jedną literę z lewej strony, aby długość pozostała taka sama. Okno nigdy nie musi się zmniejszać, atopnigdy nie musi maleć.
Rozwiązanie
Koszt jednego podciągu jest łatwy do określenia: jego długość minus liczba wystąpień najczęściej pojawiającej się w nim litery. Trudność polega na tym, by nie obliczać kosztu wszystkich n² podciągów. Okno przesuwne przegląda ciąg znaków tylko raz, a najlepsza wersja opiera się na dwóch faktach: okna nigdy nie trzeba zmniejszać, a liczba wystąpień najczęściej pojawiającej się litery nigdy nie musi maleć.
Sprawdź każdy podciąg
Poprawne, ale nie kończy się na największych testach
Intuicja
Popraw jeden fragment. Na jaką literę należy go zmienić? Na tę, która już występuje najczęściej, ponieważ każda inna litera musi się zmienić. Zatem fragment o długości len, w którym najczęściej występująca litera pojawia się top razy, wymaga len - top zmian i jest osiągalny, gdy ta wartość nie przekracza k.
Wypróbuj każdy fragment. Dla każdego początku zwiększaj koniec o jedną literę i zliczaj wystąpienia każdej litery, zwiększając przy tym top. Każdy kolejny fragment wymaga wtedy jednej aktualizacji zamiast ponownego zliczania od początku. Sprawdzane są wszystkie fragmenty, więc nie da się przeoczyć najdłuższego osiągalnego.
To rozwiązanie jest powolne, ponieważ ciąg o długości n ma około n²/2 fragmentów. Dla n = 5 × 10^4 oznacza to 1.25 × 10^9 sprawdzeń, czyli znacznie więcej, niż pozwala limit czasu.
Algorytm
- Ustaw
bestna 0. - Dla każdego indeksu początkowego wyzeruj 26 liczników i
top. - Przesuwaj
endod indeksu początkowego do ostatniego indeksu. Dodajs[end]do jego licznika i zwiększtop, jeśli ten licznik jest teraz najwyższy. - Jeśli
end - start + 1 - top ≤ k, podciąg jest osiągalny: zapisz jego długość, jeśli jest większa niżbest. - Zwróć
best.
def characterReplacement(s, k):
n = len(s)
best = 0
for start in range(n):
count = [0] * 26 # letters in s[start..end]
top = 0 # count of the most common letter there
for end in range(start, n):
c = ord(s[end]) - ord('A')
count[c] += 1
top = max(top, count[c])
# Every letter that is not the most common one must change.
if end - start + 1 - top <= k:
best = max(best, end - start + 1)
return bestJedno przesuwne okno na każdą docelową literę
Intuicja
Odwróć pytanie i najpierw wybierz literę. Jeśli końcowy ciąg składa się wyłącznie z A, pytanie brzmi: jaki jest najdłuższy podciąg zawierający co najwyżej k liter, które nie są A? To klasyczne okno przesuwne.
Przesuwaj right po ciągu i zliczaj litery wewnątrz okna, które nie są literą docelową. Gdy ich liczba przekroczy k, przesuwaj left do przodu, aż znów będzie ich k. Powiększanie okna może jedynie dodawać litery do zmiany, więc okno, którego zmiana wymaga zbyt wiele, nadal będzie wymagać zbyt wiele po powiększeniu, a left nigdy nie musi się cofać. Dla każdego right zachowywane okno jest najdłuższym poprawnym oknem kończącym się w tym miejscu.
Wykonaj to dla wszystkich 26 liter i zachowaj najlepszą długość. Każde przejście zajmuje O(n), więc łącznie jest to 26 przejść, czyli około 1.3 × 10^6 kroków dla n = 5 × 10^4. To rozwiązanie liniowe, ale odczytuje ciąg 26 razy i działa tylko dlatego, że alfabet jest mały.
Algorytm
- Dla każdej docelowej litery od
AdoZrozpocznij okno z wartościamileft = 0iothers = 0. - Przesuwaj
rightpo ciągu znaków. Jeślis[right]nie jest literą docelową, zwiększotherso jeden. - Gdy
others > k, przesuwajleftdo przodu i zmniejszotherso jeden, jeśli opuszczana litera nie jest literą docelową. - Zapisz
right - left + 1, jeśli ta wartość jest większa niżbest. - Po sprawdzeniu wszystkich 26 liter zwróć
best.
def characterReplacement(s, k):
best = 0
for target in "ABCDEFGHIJKLMNOPQRSTUVWXYZ":
left = 0
others = 0 # letters in s[left..right] that are not target
for right in range(len(s)):
if s[right] != target:
others += 1
# Too many letters to change: drop letters from the left.
while others > k:
if s[left] != target:
others -= 1
left += 1
best = max(best, right - left + 1)
return bestJedno okno, które nigdy się nie kurczy
Intuicja
Obsłuż każdą literę w jednym oknie. Zliczaj wystąpienia każdej z 26 liter w tym oknie oraz top, czyli największą liczbę wystąpień. Okno wymaga length - top zmian, więc jest poprawne, jeśli ta wartość nie przekracza k.
Pierwszy fakt: okno nigdy nie musi się zmniejszać. Zależy ci tylko na pobiciu najlepszego dotąd znalezionego wyniku, więc gdy dodanie s[right] sprawia, że okno wymaga zbyt wielu zmian, usuń jedną literę z lewej strony. Okno przesuwa się o jeden krok i zachowuje swoją długość. Gdy okno nie wymaga zbyt wielu zmian, zwiększa się o jeden. Jego długość jest więc zawsze równa najlepszej dotąd znalezionej długości, a na końcu odpowiedzią jest n - left.
Drugi fakt: wartość top nigdy nie musi maleć. Gdy litera opuszcza okno z lewej strony, nie zmieniasz top, więc może być większe niż rzeczywista liczba wystąpień w oknie. To bezpieczne. Po przesunięciu długość okna wynosi dokładnie top + k, więc jego zwiększenie wymaga litery, która występuje w oknie top + 1 razy, i wtedy top rośnie razem z nią. Nieaktualne top może spowodować przesunięcie okna, ale nigdy błędne jego zwiększenie, a przesunięcie niczego nie tracimy, bo rekord może pobić tylko dłuższe okno.
Dla BAAACAB przy k = 1 okno rośnie do BAAA, a następnie BAAAC wymaga 2 zmian, więc przesuwa się do AAAC. Dodanie kolejnego A zwiększa top do 4, a okno rośnie do AAACA o długości 5. Ostatnie B powoduje jeszcze jedno przesunięcie, więc odpowiedzią jest 5.
Algorytm
- Zachowuj liczniki dla 26 liter,
left = 0itop = 0. - Przesuwaj
rightpo ciągu: dodawajs[right]do jego licznika i zwiększajtop, jeśli ten licznik jest teraz większy. - Jeśli
right - left + 1 - top > k, okno wymaga zbyt wielu zmian: usuńs[left]z liczników i przesuńlefto jeden krok. Okno przesuwa się i zachowuje swoją długość. - Nigdy nie zmniejszaj
top, gdy litera opuszcza okno. - Zwróć długość końcowego okna,
n - left.
def characterReplacement(s, k):
count = [0] * 26 # letters inside the window s[left..right]
left = 0
top = 0 # the highest count any letter has reached in a window
for right in range(len(s)):
c = ord(s[right]) - ord('A')
count[c] += 1
top = max(top, count[c])
# Needs more than k changes: slide the window instead of growing it.
if right - left + 1 - top > k:
count[ord(s[left]) - ord('A')] -= 1
left += 1
# The window only grew when a longer valid substring was found.
return len(s) - left
Pułapki i przypadki brzegowe
Kod okna jest krótki, więc większość błędnych odpowiedzi wynika ze wzoru na koszt albo ze skrótu, który tylko wygląda na poprawny.
- Dodawanie
kdo najdłuższego ciągu. WAAABprzyk = 3daje to 6, czyli więcej niż długość ciągu. WBAAACABprzyk = 1daje to 4, ale właściwa zmiana znajduje się pośrodku i łączy dwa ciągi w jeden o długości 5. - Liczenie zmian względem pierwszej litery okna zamiast najczęściej występującej w nim litery. Okno
BAAAwymaga jednej zmiany, a nie trzech. - Zwracanie
n - leftw wersji, w której okno może się zmniejszać. Ten skrót działa tylko wtedy, gdy okno nigdy się nie skraca, jak w pokazanym tu kodzie obsługującym jedno okno. Jeśli w pętli zmniejszasz okno za pomocąwhilei ponownie obliczasz rzeczywiste maksimum, zachowaj osobnebest. - Mierzenie okna jako
right - left. Oba końce należą do okna, więc dodaj jeden. - Traktowanie
k = 0jako przypadku szczególnego. Bez zmian reguła okna już zwraca długość najdłuższego ciągu złożonego z jednej litery.
Najczęstsze pytania4
Jaka jest złożoność czasowa problemu najdłuższego powtarzającego się znaku?
Rozwiązanie z jednym oknem działa w czasie O(n), gdzie n to długość s: right odwiedza każdą literę raz, a left przesuwa się co najwyżej raz na krok. Wykorzystuje O(1) dodatkowej pamięci: 26 liczników i kilka liczb całkowitych.
Dlaczego nie trzeba aktualizować maksymalnej częstotliwości, gdy przesuwa się okno?
Okno próbuje pobić tylko własny rekord. Po przesunięciu jego długość wynosi top + k, więc aby dobre okno było dłuższe, jakaś litera musi występować więcej niż top razy, a to i tak podnosi wartość top. Zbyt wysoka wartość top jedynie utrzymuje okno na jego długości; nigdy nie sprawia, że okno rośnie, kiedy nie powinno.
Czym to się różni od problemu „Najdłuższy podciąg bez powtarzających się znaków”?
Oba przesuwają się o dwa znaki po ciągu, ale zasada określająca, czy okno jest dobre, jest inna. Tam okno jest dobre, gdy żaden znak się nie powtarza, i musi się zmniejszać, aż powtórzenie zniknie. Tutaj okno jest dobre, gdy jego długość minus liczba wystąpień najczęstszej litery wynosi co najwyżej k, co pozwala przesuwać okno o stałej długości zamiast je zmniejszać.
Czy ten problem można rozwiązać za pomocą wyszukiwania binarnego?
Tak. Jeśli jakiś podciąg o długości L jest osiągalny, to osiągalny jest również każdy krótszy podciąg w jego obrębie, więc możesz użyć wyszukiwania binarnego po L. Dla każdego L przesuwaj okno o stałej długości i sprawdzaj, czy istnieje pozycja, która wymaga najwyżej k zmian. To O(n log n), wolniejsze niż rozwiązanie z jednym oknem, ale odpowiednie jako odpowiedź.
Podobne zadania
Zadania oparte na tych samych pomysłach. Rozwiązanie dwóch lub trzech utrwala schemat.
Python
def characterReplacement(s, k):
# Napisz kod tutajPrzypadek 1
Przypadek 2
Przypadek 3
Wejście
s = "BAAACAB" k = 1
Oczekiwane
5