0Pricing
Coding Interview Prep · Lezione

Jump Game I e II

Determini la raggiungibilità e il numero minimo di salti usando un approccio greedy di espansione dell'intervallo, senza ricorrere alla DP.

Jump Game I e II è una lezione Coding Interview Prep gratuita su CoddyKit. Questa è la lezione 3 di 4. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Coding Interview Prep, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Coding Interview Prep include 4 lezioni in totale.

Jump Game I: è possibile raggiungere la fine

Jump Game I (LeetCode 55): dato un array in cui nums[i] è la lunghezza massima del salto dall'indice i, determinare se è possibile raggiungere l'ultimo indice partendo dall'indice 0. Per [2, 3, 1, 1, 4], è possibile raggiungere la fine (con il salto 2→3, poi 3 consente di arrivare alla fine). Per [3, 2, 1, 0, 4], non è possibile (si finisce sempre su 0, che ha un salto di lunghezza 0). Una soluzione greedy viene eseguita in 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

Greedy: tenere traccia della portata massima

L'intuizione greedy per Jump Game I consiste nel mantenere max_reach, l'indice più lontano raggiungibile fino a quel momento. A ogni indice i, si aggiorna max_reach = max(max_reach, i + nums[i]). Se in un qualsiasi momento i > max_reach, l'indice corrente non è raggiungibile: si restituisce False. Se si raggiunge o si supera l'ultimo indice, si restituisce True. Non servono né programmazione dinamica né backtracking.

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

Tracciare Jump Game I

Si tracci [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 → si restituisce False. L'algoritmo identifica correttamente che l'indice 4 non è raggiungibile. Ogni percorso dall'indice 0 rimane intrappolato, perché lo 0 all'indice 3 limita max_reach a 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: numero minimo di salti

Jump Game II (LeetCode 45) richiede di trovare il numero minimo di salti per raggiungere l'ultimo indice, che è sempre raggiungibile. L'approccio greedy utilizza una strategia di espansione dell'intervallo: si mantengono la portata massima del salto corrente (curr_end) e la portata massima del salto successivo (farthest). Quando si esaurisce l'intervallo del salto corrente, è necessario effettuare un salto: si incrementa jumps e si imposta 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

Visualizzare Jump Game II

Si consideri Jump Game II come un approccio BFS livello per livello, senza il sovraccarico della coda. Ogni salto corrisponde a un livello BFS. curr_end è il limite del livello corrente. farthest è l'indice massimo raggiungibile al livello successivo. Quando si termina la scansione del livello corrente (i == curr_end), si è determinato il limite del livello successivo e si deve incrementare il conteggio dei salti. Si tratta di una BFS su un grafo implicito con complessità temporale O(n) e spazio 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]))

Perché la strategia greedy è corretta per Jump II

Perché la strategia greedy, che estende sempre la portata fino al punto più lontano, restituisce il numero minimo di salti? Argomento dello scambio: supponiamo che la soluzione ottimale effettui un salto che non raggiunge il punto più lontano. È sempre possibile estendere quel salto fino a farthest senza costi aggiuntivi: rimane comunque un solo salto. Scegliendo sempre la portata massima per ogni salto, si garantisce il numero minimo di salti necessario. Una soluzione che percorresse una distanza minore a ogni salto non potrebbe fare meglio e avrebbe bisogno di più salti per coprire la stessa distanza.

# 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))

Alternativa con programmazione dinamica per Jump Game II

Una soluzione con programmazione dinamica: dp[i] = numero minimo di salti per raggiungere l'indice i. Per ogni posizione j, si aggiornano tutte le posizioni raggiungibili: dp[j+k] = min(dp[j+k], dp[j]+1) per k in 1..nums[j]. Questa soluzione richiede tempo O(n × max_jump) e spazio O(n), quindi è molto più lenta della soluzione greedy O(n). In questo caso la soluzione greedy è superiore; la programmazione dinamica viene mostrata a confronto, per illustrare come la strategia greedy possa evitare il ciclo interno.

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: raggiungere l'indice zero

Jump Game III (LeetCode 1306): si parte da un indice dato; dall'indice i è possibile saltare a i + nums[i] oppure a i - nums[i]. È possibile raggiungere un indice qualsiasi con valore 0? Questo è un problema di raggiungibilità (BFS/DFS), non di minimizzazione: la strategia greedy non è applicabile. Si utilizzi la BFS con un insieme degli indici visitati per evitare i cicli. Tempo: 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: raggiungibilità con un intervallo

Jump Game VII (LeetCode 1871): è possibile attraversare una stringa binaria saltando dall'indice 0 all'ultimo indice, quando dalla posizione i è possibile saltare a qualsiasi '0' in [i+minJump, i+maxJump]? Si utilizzi una somma su finestra scorrevole sull'array degli indici raggiungibili. Si mantenga una somma prefissa delle posizioni raggiungibili; una posizione j è raggiungibile se esiste una posizione raggiungibile in [j-maxJump, j-minJump].

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

Confronto tra le soluzioni greedy e BFS

Jump Game II ammette due approcci equivalenti con complessità O(n): l'espansione greedy dell'intervallo e la visita BFS per livelli. L'approccio greedy usa spazio O(1) (non richiede una coda), mentre la BFS usa O(n) per l'insieme degli indici visitati. In un colloquio, si preferisce la soluzione greedy per la sua efficienza spaziale. Tuttavia, la BFS è più semplice da ricavare inizialmente: se non si riesce a individuare la soluzione greedy, si scriva una BFS per ottenere una soluzione funzionante e poi la si ottimizzi. Entrambe calcolano correttamente il numero minimo di salti.

# 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

Riepilogo della complessità di Jump Game

Riepilogo della complessità delle varianti di Jump Game: Jump I (raggiungibilità): tempo O(n), spazio O(1). Jump II (minimo numero di salti greedy): tempo O(n), spazio O(1). Jump II (BFS): tempo O(n), spazio O(n). Jump II (programmazione dinamica): tempo O(n × max_jump), spazio O(n). Jump III (BFS/DFS): tempo O(n), spazio O(n) per gli indici visitati. Jump VII (finestra scorrevole): tempo O(n), spazio O(n). Nei colloqui, presenti sempre la soluzione greedy O(n) O(1) per Jump I e 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')

Verifica rapida

Verifichi la propria comprensione dei concetti di Data Structures & Algorithms — Coding Interview Prep presentati in questa lezione.

Riepilogo della lezione

In questa lezione ha imparato che: Jump Game I usa il tracciamento greedy di max_reach per determinare la raggiungibilità in tempo O(n) e spazio O(1); Jump Game II usa l'espansione dell'intervallo con curr_end e farthest per contare il numero minimo di salti in O(n) e O(1); e l'espansione greedy dell'intervallo equivale a una BFS livello per livello senza il sovraccarico della coda. Nella prossima lezione applicheremo il ragionamento greedy al periodo di cooldown di Task Scheduler e ai problemi di fattibilità circolare di Gas Station.

Domande Frequenti

La lezione «Jump Game I e II» è gratuita?

Sì — il testo completo di «Jump Game I e II» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Coding Interview Prep, passa a CoddyKit PRO. Il corso Coding Interview Prep include 4 lezioni in totale.

Cosa imparerò in «Jump Game I e II»?

Determini la raggiungibilità e il numero minimo di salti usando un approccio greedy di espansione dell'intervallo, senza ricorrere alla DP. Eserciti Coding Interview Prep con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare Coding Interview Prep?

Non è richiesta alcuna esperienza precedente. Coding Interview Prep su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 3 di 4.

Quanto tempo richiede la lezione «Jump Game I e II»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione Coding Interview Prep?

Sì. Ogni lezione Coding Interview Prep include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Greedy e DP: quando usare ciascuno
  2. Pianificazione e fusione degli intervalli
  3. Jump Game I e II
  4. Task Scheduler e Gas Station
← Torna a Coding Interview Prep