0Pricing
Coding Interview Prep · Lekcja

Jump Game I i II

Określać osiągalność i minimalną liczbę skoków za pomocą zachłannego rozszerzania zasięgu, bez potrzeby stosowania programowania dynamicznego

Jump Game I i II to bezpłatna lekcja Coding Interview Prep na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Coding Interview Prep, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Coding Interview Prep zawiera 4 lekcji w sumie.

Jump Game I: Czy można dotrzeć do końca?

Jump Game I (LeetCode 55): dana jest tablica, w której nums[i] oznacza maksymalną długość skoku z indeksu i. Należy ustalić, czy można dotrzeć do ostatniego indeksu, zaczynając od indeksu 0. Dla [2, 3, 1, 1, 4] można dotrzeć do końca (skok o 2 prowadzi do indeksu 2, a następnie skok o 3 prowadzi do końca). Dla [3, 2, 1, 0, 4] nie jest to możliwe (ostatecznie zawsze trafia się na 0, z którego nie można skoczyć). Rozwiązanie zachłanne działa w czasie O(n).

# Can you reach the last index?
nums1 = [2, 3, 1, 1, 4]  # True: 0→1→4 or 0→2→3→4
nums2 = [3, 2, 1, 0, 4]  # False: always land on index 3 (value 0)

# At index 3 (value 0): no matter how you get here,
# you can't jump further to reach index 4
print('nums1 last index:', len(nums1)-1)
print('nums2 index 3 jump value:', nums2[3])  # 0 = stuck

Podejście zachłanne: śledzenie maksymalnego zasięgu

Kluczowa obserwacja w Jump Game I: należy utrzymywać max_reach, czyli najdalszy indeks, do którego można dotrzeć na danym etapie. Dla każdego indeksu i aktualizujemy max_reach = max(max_reach, i + nums[i]). Jeśli w dowolnym momencie i > max_reach, bieżący indeks jest nieosiągalny — należy zwrócić False. Jeśli dotrzemy do ostatniego indeksu lub go przekroczymy, należy zwrócić True. Nie jest potrzebne programowanie dynamiczne ani nawroty.

def can_jump(nums):
    max_reach = 0
    for i, jump in enumerate(nums):
        if i > max_reach:      # can't reach index i
            return False
        max_reach = max(max_reach, i + jump)
        if max_reach >= len(nums) - 1:
            return True  # early exit
    return True

print(can_jump([2, 3, 1, 1, 4]))  # True
print(can_jump([3, 2, 1, 0, 4]))  # False
print(can_jump([0]))              # True (already at last index)
print(can_jump([1, 0, 0]))        # False

Śledzenie przebiegu Jump Game I

Prześledźmy [3, 2, 1, 0, 4]: i=0, jump=3, max_reach=3. i=1, jump=2, max_reach=max(3,3)=3. i=2, jump=1, max_reach=max(3,3)=3. i=3, jump=0, max_reach=max(3,3)=3. i=4, i=4 > max_reach=3 → zwracamy False. Algorytm poprawnie stwierdza, że indeks 4 jest nieosiągalny. Każda ścieżka z indeksu 0 zostaje uwięziona, ponieważ wartość 0 na indeksie 3 ogranicza max_reach do 3.

def can_jump_trace(nums):
    max_reach = 0
    for i, jump in enumerate(nums):
        print(f'i={i}, jump={jump}, max_reach before={max_reach}', end='')
        if i > max_reach:
            print(' → UNREACHABLE')
            return False
        max_reach = max(max_reach, i + jump)
        print(f' → max_reach={max_reach}')
    return True

print('Result:', can_jump_trace([3, 2, 1, 0, 4]))

Jump Game II: Minimalna liczba skoków

Jump Game II (LeetCode 45) wymaga znalezienia minimalnej liczby skoków potrzebnych do dotarcia do ostatniego indeksu (który zawsze jest osiągalny). Podejście zachłanne wykorzystuje strategię rozszerzania zasięgu: należy utrzymywać najdalszy zasięg bieżącego skoku (curr_end) oraz najdalszy zasięg następnego skoku (farthest). Gdy wyczerpiemy zasięg bieżącego skoku, musimy wykonać skok — zwiększamy jumps i ustawiamy curr_end = farthest.

def jump(nums):
    n = len(nums)
    if n == 1: return 0  # already at destination
    jumps = 0
    curr_end = 0   # end of current jump's range
    farthest = 0   # farthest reachable in next jump
    for i in range(n - 1):  # don't jump from last index
        farthest = max(farthest, i + nums[i])
        if i == curr_end:    # exhausted current jump range
            jumps += 1
            curr_end = farthest
            if curr_end >= n - 1: break
    return jumps

print(jump([2, 3, 1, 1, 4]))  # 2 (0→1→4)
print(jump([2, 3, 0, 1, 4]))  # 2 (0→1→4)
print(jump([1, 2, 1, 1, 1])) # 3

Wizualizacja Jump Game II

Jump Game II można rozumieć jako podejście BFS poziom po poziomie, bez narzutu związanego z kolejką. Każdy skok odpowiada jednemu poziomowi BFS. curr_end jest granicą bieżącego poziomu. farthest oznacza maksymalny indeks osiągalny na następnym poziomie. Po zakończeniu przeglądania bieżącego poziomu (i == curr_end) znamy granicę następnego poziomu i musimy zwiększyć liczbę skoków. Jest to BFS na grafie niejawnym o złożoności czasowej O(n) i pamięciowej O(1).

def jump_traced(nums):
    n = len(nums)
    jumps = curr_end = farthest = 0
    for i in range(n - 1):
        farthest = max(farthest, i + nums[i])
        print(f'i={i}: farthest={farthest}, curr_end={curr_end}')
        if i == curr_end:
            jumps += 1
            curr_end = farthest
            print(f'  → JUMP #{jumps}, new range ends at {curr_end}')
            if curr_end >= n - 1: break
    return jumps

print('Min jumps:', jump_traced([2, 3, 1, 1, 4]))

Dlaczego podejście zachłanne jest poprawne dla Jump II

Dlaczego podejście zachłanne, które zawsze rozszerza zasięg do najdalszego punktu, daje minimalną liczbę skoków? Argument wymiany: załóżmy, że rozwiązanie optymalne wykonuje skok, który nie dociera do najdalszego punktu. Zawsze można rozszerzyć ten skok tak, aby dotarł do farthest, bez dodatkowego kosztu — nadal jest to jeden skok. Wybierając przy każdym skoku maksymalny zasięg, gwarantujemy minimalną potrzebną liczbę skoków. Żadne rozwiązanie, które przy każdym skoku pokonuje mniejszy zasięg, nie może być lepsze i potrzebowałoby większej liczby skoków, aby pokonać tę samą odległość.

# Correctness verification: compare to BFS
from collections import deque

def jump_bfs(nums):
    n = len(nums)
    if n == 1: return 0
    visited = [False] * n
    visited[0] = True
    queue = deque([0])
    level = 0
    while queue:
        level += 1
        for _ in range(len(queue)):
            pos = queue.popleft()
            for j in range(1, nums[pos] + 1):
                nxt = pos + j
                if nxt >= n - 1: return level
                if not visited[nxt]:
                    visited[nxt] = True
                    queue.append(nxt)
    return -1

# Both should give same results
for nums in [[2,3,1,1,4],[2,3,0,1,4],[1,2,1,1,1]]:
    print(jump(nums), '==', jump_bfs(nums))

Alternatywne rozwiązanie DP dla Jump Game II

Rozwiązanie DP: dp[i] oznacza minimalną liczbę skoków potrzebnych do dotarcia do indeksu i. Dla każdej pozycji j aktualizujemy wszystkie osiągalne pozycje: dp[j+k] = min(dp[j+k], dp[j]+1) dla k z zakresu 1..nums[j]. Rozwiązanie działa w czasie O(n × max_jump) i zajmuje O(n) pamięci — jest więc znacznie wolniejsze niż zachłanne rozwiązanie O(n). Podejście zachłanne jest tutaj lepsze; DP pokazano dla porównania, aby zilustrować, jak podejście zachłanne może pominąć pętlę wewnętrzną.

def jump_dp(nums):
    n = len(nums)
    dp = [float('inf')] * n
    dp[0] = 0
    for j in range(n):
        for k in range(1, nums[j] + 1):
            if j + k < n:
                dp[j+k] = min(dp[j+k], dp[j] + 1)
    return dp[n-1]

print(jump_dp([2, 3, 1, 1, 4]))  # 2
print(jump_dp([1, 2, 1, 1, 1]))  # 3

# Greedy is O(n), DP is O(n * max_jump)
# For large inputs with big jump values, greedy is much faster

Jump Game III: Dotarcie do indeksu zero

Jump Game III (LeetCode 1306): zaczynając od danego indeksu, z indeksu i można przejść do i + nums[i] lub i - nums[i]. Czy można dotrzeć do dowolnego indeksu o wartości 0? Jest to problem osiągalności (BFS/DFS), a nie problem minimalizacji — podejście zachłanne nie ma tu zastosowania. Należy użyć BFS wraz ze zbiorem odwiedzonych indeksów, aby uniknąć cykli. Złożoność czasowa: O(n).

from collections import deque

def can_reach(arr, start):
    n = len(arr)
    visited = set()
    queue = deque([start])
    while queue:
        idx = queue.popleft()
        if arr[idx] == 0: return True
        if idx in visited: continue
        visited.add(idx)
        for nxt in [idx + arr[idx], idx - arr[idx]]:
            if 0 <= nxt < n and nxt not in visited:
                queue.append(nxt)
    return False

print(can_reach([4,2,3,0,3,1,2], 5))  # True (5→4→1→3, arr[3]=0)
print(can_reach([3,0,2,1,2], 2))      # False (can't reach index 1, arr[1]=0)

Jump Game VII: Osiągalność w określonym zakresie

Jump Game VII (LeetCode 1871): czy można przejść przez binarny napis, skacząc z indeksu 0 do ostatniego indeksu, jeśli z pozycji i można skoczyć na dowolny znak '0' w zakresie [i+minJump, i+maxJump]? Należy użyć sumy w przesuwanym oknie dla tablicy osiągalności. Utrzymujemy sumę prefiksową pozycji osiągalnych; pozycja j jest osiągalna, jeśli w zakresie [j-maxJump, j-minJump] znajduje się pozycja osiągalna.

def can_reach_vii(s, min_jump, max_jump):
    n = len(s)
    reach = [False] * n
    reach[0] = True
    pre = [0] * (n + 1)  # prefix sum of reachable positions
    pre[1] = 1
    for j in range(1, n):
        # Window sum: any reachable position in [j-maxJump, j-minJump]?
        lo = max(0, j - max_jump)
        hi = max(0, j - min_jump + 1)
        window_sum = pre[hi] - pre[lo]
        if s[j] == '0' and window_sum > 0:
            reach[j] = True
        pre[j+1] = pre[j] + (1 if reach[j] else 0)
    return reach[n-1]

print(can_reach_vii('011010', 2, 3))  # True
print(can_reach_vii('01101110', 2, 3))  # False

Porównanie rozwiązań zachłannych i BFS

Jump Game II ma dwa równoważne podejścia o złożoności O(n): zachłanne rozszerzanie zasięgu oraz przechodzenie BFS poziomami. Podejście zachłanne używa O(1) pamięci (bez kolejki), natomiast BFS wymaga O(n) pamięci na zbiór odwiedzonych pozycji. Podczas rozmowy kwalifikacyjnej preferowane jest podejście zachłanne ze względu na efektywność pamięciową. BFS jest jednak łatwiejszy do wyprowadzenia — jeśli trudno dostrzec rozwiązanie zachłanne, można najpierw zaimplementować BFS, aby uzyskać działające rozwiązanie, a następnie je zoptymalizować. Oba podejścia poprawnie obliczają minimalną liczbę skoków.

# Both approaches are O(n) time
# Greedy: O(1) space — preferred in interviews
# BFS: O(n) space — easier to derive

# Greedy advantage: no auxiliary data structures
def jump_greedy(nums):
    n, jumps, curr, far = len(nums), 0, 0, 0
    for i in range(n-1):
        far = max(far, i+nums[i])
        if i == curr: jumps += 1; curr = far
    return jumps

# BFS equivalence: each level = one jump
from collections import deque
def jump_bfs(nums):
    n = len(nums)
    if n == 1: return 0
    q, visited, level = deque([0]), {0}, 0
    while q:
        level += 1
        for _ in range(len(q)):
            pos = q.popleft()
            for j in range(1, nums[pos]+1):
                nxt = pos + j
                if nxt >= n-1: return level
                if nxt not in visited: visited.add(nxt); q.append(nxt)
    return -1

nums = [2,3,1,1,4]
print(jump_greedy(nums), '==', jump_bfs(nums))  # both 2

Podsumowanie złożoności Jump Game

Podsumowanie złożoności różnych wariantów Jump Game: Jump I (osiągalność): czas O(n), pamięć O(1). Jump II (minimalna liczba skoków, podejście zachłanne): czas O(n), pamięć O(1). Jump II (BFS): czas O(n), pamięć O(n). Jump II (DP): czas O(n × max_jump), pamięć O(n). Jump III (BFS/DFS): czas O(n), pamięć O(n) na zbiór odwiedzonych pozycji. Jump VII (przesuwane okno): czas O(n), pamięć O(n). Na rozmowach kwalifikacyjnych zawsze należy przedstawiać zachłanne rozwiązanie O(n), O(1) dla Jump I i II.

# Comparison: all versions on the same input
nums = [2, 3, 1, 1, 4]

# Jump I
def can_jump(nums):
    mr = 0
    for i, j in enumerate(nums):
        if i > mr: return False
        mr = max(mr, i+j)
    return True

# Jump II greedy O(n) O(1)
def jump_min(nums):
    n, jumps, curr, far = len(nums), 0, 0, 0
    for i in range(n-1):
        far = max(far, i+nums[i])
        if i == curr:
            jumps += 1; curr = far
            if curr >= n-1: break
    return jumps

print('Can reach:', can_jump(nums))    # True
print('Min jumps:', jump_min(nums))    # 2
print('Complexity: O(n) time, O(1) space')

Szybkie sprawdzenie

Sprawdź swoje zrozumienie zagadnień z kursu Data Structures & Algorithms — Coding Interview Prep omawianych w tej lekcji.

Podsumowanie lekcji

W tej lekcji nauczyli się Państwo: wykorzystywać w Jump Game I zachłanne śledzenie max_reach do określania osiągalności w czasie O(n) i przy użyciu O(1) pamięci, wykorzystywać w Jump Game II rozszerzanie zasięgu z wartościami curr_end i farthest do obliczania minimalnej liczby skoków w czasie O(n) i przy użyciu O(1) pamięci oraz że zachłanne rozszerzanie zasięgu jest równoważne przechodzeniu BFS poziom po poziomie, ale bez narzutu związanego z kolejką. Następnie zastosujemy rozumowanie zachłanne do problemu Task Scheduler z okresem oczekiwania oraz problemu Gas Station dotyczącego możliwości pokonania całego okręgu.

Często zadawane pytania

Czy lekcja „Jump Game I i II” jest bezpłatna?

Tak — pełny tekst „Jump Game I i II” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Coding Interview Prep, przejdź na CoddyKit PRO. Kurs Coding Interview Prep zawiera 4 lekcji w sumie.

Co nauczysz się w „Jump Game I i II”?

Określać osiągalność i minimalną liczbę skoków za pomocą zachłannego rozszerzania zasięgu, bez potrzeby stosowania programowania dynamicznego Ćwiczysz Coding Interview Prep z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć Coding Interview Prep?

Nie wymagamy żadnego doświadczenia. Coding Interview Prep w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.

Ile czasu zajmuje lekcja „Jump Game I i II”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji Coding Interview Prep?

Tak. Każda lekcja Coding Interview Prep zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. Algorytm zachłanny a programowanie dynamiczne: kiedy stosować które podejście
  2. Harmonogramowanie i scalanie przedziałów
  3. Jump Game I i II
  4. Task Scheduler i Gas Station
← Powrót do Coding Interview Prep