Jump Game
Znajdujesz się na indeksie 0 tablicy nums. Z indeksu i możesz skoczyć do przodu o dowolną liczbę kroków od 1 do nums[i], więc nums[i] oznacza najdłuższy możliwy skok z tego miejsca, a 0 oznacza, że nie możesz się poruszyć. Zwróć true, jeśli jakaś sekwencja skoków pozwala dotrzeć do ostatniego indeksu, a w przeciwnym razie false.
Funkcja
- numsinteger-array
- najdłuższy skok, jaki możesz wykonać z każdego indeksu
- Zwracaboolean
- true, jeśli możesz dotrzeć do ostatniego indeksu, zaczynając od indeksu 0, w przeciwnym razie false
Ograniczenia
1 ≤ nums.length ≤ 1040 ≤ nums[i] ≤ 105- Skok może być krótszy niż
nums[i], więc długi skok nigdy nie zmusza cię do przeskoczenia ostatniego indeksu.
Przykłady
- Wejście
- nums = [2, 0, 3, 1, 0, 2]
- Wyjście
- true
- Wyjaśnienie
- Z indeksu 0 możesz przejść do indeksu 1 lub 2. Indeks 1 zawiera 0 i jest ślepą uliczką, ale indeks 2 zawiera 3 i prowadzi do indeksu 5, ostatniego indeksu.
- Wejście
- nums = [1, 3, 0, 0, 0, 2]
- Wyjście
- false
- Wyjaśnienie
- Indeks 0 może przejść tylko do indeksu 1, a indeks 1 sięga najwyżej do indeksu 4. Indeksy 2, 3 i 4 zawierają 0, więc nic nigdy nie dociera poza indeks 4 do indeksu 5.
- Wejście
- nums = [0]
- Wyjście
- true
- Wyjaśnienie
- Tablica ma jeden element, więc zaczynasz od ostatniego indeksu i nie musisz w ogóle przeskakiwać.
+18 ukrytych testów przy wysłaniu
Pytanie dodatkowe
Policz różne sekwencje skoków, które prowadzą na ostatni indeks, modulo 10^9+7, nadal w czasie O(n).
Podpowiedzi
Otwieraj je po kolei. Każda zdradza trochę więcej.
0zastawia na ciebie pułapkę tylko wtedy, gdy nic przed nim nie może przeskoczyć go. Co musisz wiedzieć o indeksach przed nim, żeby to stwierdzić?Jeśli możesz dotrzeć do indeksu
i, możesz dotrzeć do każdego indeksu odidoi+nums[i], ponieważ dozwolone są krótsze skoki. Zatem osiągalne indeksy zawsze tworzą jeden nieprzerwany blok zaczynający się od indeksu 0.Przechodź od lewej do prawej i zapamiętuj
farthest, prawy koniec tego bloku. Jeśli bieżący indeks znajduje się zafarthest, nigdy nie da się do niego dotrzeć. W przeciwnym razie przesuwajfarthestdoi+nums[i], jeśli ta wartość jest większa. Jeśli przejdziesz przez całą tablicę, można dotrzeć do ostatniego indeksu.
Rozwiązanie
Liczba możliwych tras rośnie wykładniczo, więc sprawdzanie ich po kolei nie sprawdzi się w przypadku długich tablic. Kluczowy jest fakt, że indeksy, do których możesz dotrzeć, zawsze tworzą jeden nieprzerwany blok zaczynający się od indeksu 0. Jedna liczba — prawy koniec tego bloku — zawiera wszystko, czego potrzebujesz, a jeden przebieg pozwala ustalić odpowiedź.
Wypróbuj każdy skok
Poprawne, ale nie kończy się na największych testach
Intuicja
Najbardziej bezpośredni pomysł to odegrać to. Stań na indeksie 0 i po kolei wypróbuj każde miejsce lądowania, na które pozwala skok. Powtórz to samo dla każdego miejsca lądowania. Jeśli w którejkolwiek gałęzi dotrzesz do ostatniego indeksu, odpowiedzią jest true. Jeśli każda gałąź kończy się ślepym zaułkiem, odpowiedzią jest false.
W pierwszym przykładzie indeks 0 zawiera 2, więc wypróbowujesz indeks 1 i indeks 2. Indeks 1 zawiera 0, czyli ślepy zaułek, więc cofasz się i wypróbowujesz indeks 2. Indeks 2 zawiera 3 i pozwala dotrzeć do indeksu 5, czyli ostatniego indeksu, więc wyszukiwanie kończy się wynikiem true.
Wyszukiwanie jest poprawne, ponieważ sprawdza każdą trasę. Na tym polega też jego problem: nie zapamiętuje indeksu, który już sprawdziło, więc ponownie sprawdza ten sam indeks dla każdej trasy, która do niego prowadzi. Gdy odpowiedzią jest false, musi wykluczyć każdą trasę. W [4, 3, 2, 1, 0, 5] każdy indeks przed 0 pozwala dotrzeć do 0, co daje 8 różnych tras prowadzących do niego. Przy 30 takich indeksach istnieje ponad 500 milionów tras, a największe testy mają 10,000 elementów. Tak długa trasa powoduje też przepełnienie stosu wywołań w niektórych językach: Python domyślnie zatrzymuje się po 1,000 zagnieżdżonych wywołaniach.
Algorytm
- Napisz funkcję pomocniczą
reach(i), która odpowiada na pytanie: czy można przejść z indeksuido ostatniego indeksu? - Jeśli
ijest ostatnim indeksem, zwróćtrue. - W przeciwnym razie wypróbuj każde możliwe miejsce lądowania
nextodi+1domin(i+nums[i], n-1)i zwróćtrue, gdy tylko zrobi toreach(next). - Jeśli żadne miejsce lądowania nie zadziała, zwróć
false. - Odpowiedzią jest
reach(0).
def canJump(nums):
last = len(nums) - 1
def reach(i):
# Can you get from index i to the last index?
if i == last:
return True
for nxt in range(i + 1, min(i + nums[i], last) + 1):
if reach(nxt):
return True
return False
return reach(0)Pamiętaj, które indeksy mogą zakończyć działanie
Poprawne, ale nie kończy się na największych testach
Intuicja
Powyższe wyszukiwanie wciąż zadaje to samo pytanie: „czy indeks j może zakończyć?”. Odpowiedź dla j nigdy się nie zmienia, więc ustal ją raz i zapisz. Indeks nazywamy dobrym, jeśli można z niego dotrzeć do ostatniego indeksu. Ostatni indeks jest dobry. Każdy inny indeks i jest dobry, jeśli co najmniej jeden indeks, na który można z niego trafić — od i+1 do i+nums[i] — jest dobry.
Każdy indeks zależy tylko od indeksów po swojej prawej stronie, więc wypełniaj tablicę good od prawej do lewej. W pierwszym przykładzie indeks 5 jest dobry. Indeks 4 ma wartość 0, więc nie jest dobry. Indeks 3 prowadzi tylko do indeksu 4, który nie jest dobry. Indeks 2 prowadzi do indeksów 3, 4 i 5, a indeks 5 jest dobry, więc indeks 2 też jest dobry. Indeks 1 ma wartość 0, więc nie jest dobry. Indeks 0 prowadzi do indeksów 1 i 2, a indeks 2 jest dobry, więc odpowiedzią jest true.
Teraz każdy indeks jest rozstrzygany tylko raz, ale jego rozstrzygnięcie nadal może wymagać sprawdzenia do n komórek. W przypadku [9998, 9997, …, 1, 0, 7] z każdego indeksu można dotrzeć do 0, ale nie do żadnego indeksu za nim, więc każdy indeks sprawdza cały swój zakres i nie znajduje w nim niczego dobrego. Daje to około 5 × 10^7 sprawdzeń dla 10 000 elementów, a największe testy są skonstruowane właśnie w ten sposób. Ilość pracy rośnie proporcjonalnie do kwadratu długości, więc ten algorytm nie mieści się w limicie czasu dla tych testów.
Algorytm
- Utwórz tablicę wartości logicznych
goodo długościni ustawgood[n-1]na true. - Przejdź przez
iodn-2do 0. - Sprawdzaj
jodi+1domin(i+nums[i], n-1). Jeśli którykolwiek elementgood[j]ma wartość true, ustawgood[i]na true i zakończ sprawdzanie. - Zwróć
good[0].
def canJump(nums):
n = len(nums)
good = [False] * n # good[i]: from i you can reach the last index
good[n - 1] = True
for i in range(n - 2, -1, -1):
for j in range(i + 1, min(i + nums[i], n - 1) + 1):
if good[j]:
good[i] = True
break
return good[0]Śledź najdalszy osiągalny indeks
Intuicja
Patrz na indeksy, do których możesz dotrzeć, a nie na trasy. Z indeksu i możesz trafić na dowolny indeks od i+1 do i+nums[i], bez żadnych luk. Gdy więc indeks i jest osiągalny, osiągalny jest też każdy indeks aż do i+nums[i]. Zacznij od samego indeksu 0 i dodawaj kolejne takie zakresy. Każdy nowy zakres zaczyna się wewnątrz bloku, który już masz, więc osiągalne indeksy zawsze tworzą jeden ciągły blok: [0, farthest].
Dlatego wystarczy jedna liczba. Przechodź przez kolejne indeksy i, od lewej do prawej. Dopóki i ≤ farthest, indeks i jest osiągalny, więc zwiększaj zakres, aktualizując farthest do max(farthest, i+nums[i]). Jeśli i kiedykolwiek przekroczy farthest, żaden osiągalny indeks nie pozwala dotrzeć do i. Blok nie może powiększyć się przez tę lukę, więc żaden indeks na prawo od niej nie jest osiągalny, włącznie z ostatnim indeksem. Jeśli przejdziesz do końca bez napotkania luki, ostatni indeks jest osiągalny.
W drugim przykładzie farthest wynosi 0, potem 1 po indeksie 0, a następnie 4 po indeksie 1. Indeksy 2, 3 i 4 zawierają 0, więc jego wartość pozostaje równa 4. Indeks 5 leży za 4, więc odpowiedź to false. W pierwszym przykładzie indeks 2 zwiększa wartość farthest do 5 i żaden indeks nigdy jej nie przekracza, więc odpowiedź to true.
Dlaczego bezpiecznie jest przechowywać tylko największy zasięg? Nie decydujesz się na żaden konkretny skok. Blok zawiera każdy indeks osiągalny z dowolnej trasy, a każde krótsze miejsce lądowania znajduje się w jego obrębie. Odrzucenie wszystkich informacji poza prawym końcem nie powoduje utraty żadnych danych.
Algorytm
- Ustaw
farthest = 0. - Dla każdego indeksu
i, od lewej do prawej: jeślii > farthest, zwróćfalse. - W przeciwnym razie ustaw
farthest = max(farthest, i+nums[i]). - Jeśli pętla się zakończy, każdy indeks był osiągalny, więc zwróć
true.
def canJump(nums):
farthest = 0 # every index up to farthest can be reached
for i, jump in enumerate(nums):
if i > farthest:
return False # nothing reachable jumps to i
farthest = max(farthest, i + jump)
return True
Pułapki i przypadki brzegowe
Większość błędnych odpowiedzi wynika z odczytywania nums[i] jako jedynego skoku albo z kolejności dwóch sprawdzeń wewnątrz pętli.
- Zawsze wykonywanie skoku dokładnie o
nums[i]kroków albo zawsze wybieranie najdłuższego skoku. W przypadku[2, 5, 0, 0]pełny skok z indeksu 0 ląduje na 0, podczas gdy skok o 1 krok do indeksu 1 pozwala dotrzeć do końca. - Zwracanie
falseod razu po napotkaniu 0. Wartość 0 ma znaczenie tylko wtedy, gdy żaden wcześniejszy skok nie przeskakuje nad nią:[2, 0, 1]przeskakuje nad 0, więc odpowiedź totrue. - Aktualizowanie
farthestprzed sprawdzeniemi > farthest. Indeks, do którego nie możesz dotrzeć, nie może poszerzać osiągalnego zakresu, więc najpierw sprawdź, a potem zaktualizuj. - Traktowanie tablicy jednoelementowej jako porażki. Już stoisz na ostatnim indeksie, więc odpowiedź to
true, nawet gdy ten element ma wartość 0. - Rekurencyjne przetwarzanie długich tablic. Trasa może mieć 10,000 skoków, co w kilku językach przepełnia stos wywołań. Przejście jednym przebiegiem nie wymaga rekurencji.
Najczęstsze pytania4
Jaka jest złożoność czasowa Jump Game?
Przejście sprawdzające najdalszy zasięg odwiedza każdy indeks jeden raz, więc działa w czasie O(n) i wymaga dodatkowej przestrzeni O(1). Podejście z tabelą ma w najgorszym przypadku złożoność O(n²), a wypróbowanie każdej trasy wymaga czasu wykładniczego.
Dlaczego podejście zachłanne działa w grze Jump Game?
Ponieważ dozwolone są krótsze skoki, dotarcie do indeksu i oznacza, że możesz dotrzeć do każdego indeksu aż do i+nums[i]. Te odcinki zawsze pokrywają się z już osiągniętą częścią, więc osiągalne indeksy tworzą jeden blok zaczynający się od 0. Przebieg zachłanny śledzi tylko prawy koniec tego bloku, który opisuje cały blok, więc nigdy nie odrzuca trasy, która mogłaby się sprawdzić.
Czy Jump Game jest problemem programowania dynamicznego?
Można to rozwiązać za pomocą programowania dynamicznego: oznacz każdy indeks jako dobry, jeśli jedno z pól, na które można z niego skoczyć, jest dobre, wypełniając tabelę od prawej do lewej. Zajmuje to O(n²). Zauważ, że liczy się tylko najbardziej lewy dobry indeks, ponieważ każdy indeks, z którego można dotrzeć do dobrego indeksu, pozwala też dotrzeć do najbardziej lewego z nich. Zachowaj tylko ten indeks, goal, i przesuwaj go do i, gdy i+nums[i] ≥ goal. Odpowiedzią jest informacja, czy goal ostatecznie osiągnie 0 — to przejście w czasie O(n), które odzwierciedla podejście zachłanne.
Jak znaleźć minimalną liczbę skoków?
Zastosuj tę samą ideę najdalszego zasięgu warstwami. Zachowuj koniec bloku, do którego możesz dotrzeć przy obecnej liczbie skoków, oraz najdalszy indeks, do którego możesz dotrzeć następnym skokiem. Gdy i przekroczy koniec bieżącego bloku, potrzebujesz jeszcze jednego skoku, a następny blok kończy się na tym najdalszym indeksie. Nadal jest to jedno przejście O(n).
Podobne zadania
Zadania oparte na tych samych pomysłach. Rozwiązanie dwóch lub trzech utrwala schemat.
Python
def canJump(nums):
# Wpisz kod tutajPrzypadek 1
Przypadek 2
Przypadek 3
Wejście
nums = [2, 0, 3, 1, 0, 2]
Oczekiwane
true