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 = stuckGreedy: 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])) # FalseTracciare 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])) # 3Visualizzare 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 fasterJump 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)) # FalseConfronto 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 2Riepilogo 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
- Greedy e DP: quando usare ciascuno
- Pianificazione e fusione degli intervalli
- Jump Game I e II
- Task Scheduler e Gas Station