Percorsi unici e somma minima dei percorsi nelle griglie
Compili una tabella DP 2D per i percorsi unici, con e senza ostacoli, poi la adatti per minimizzare la somma dei valori lungo un percorso
Percorsi unici e somma minima dei percorsi nelle griglie è una lezione Coding Interview Prep gratuita su CoddyKit. Questa è la lezione 1 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.
Percorsi unici su una griglia
Unique Paths (LeetCode 62) chiede: in una griglia m×n, quanti percorsi distinti vanno dall'angolo in alto a sinistra a quello in basso a destra, se ci si può muovere solo a destra o verso il basso? Per una griglia 3×7 la risposta è 28. L'intuizione chiave è che ogni percorso verso la cella (i,j) deve provenire da (i-1,j) (dall'alto) oppure da (i,j-1) (da sinistra), dando luogo a una formulazione naturale della DP 2D.
# 3x7 grid: robot starts at (0,0), goes to (2,6)
# Must make exactly 2 down-moves and 6 right-moves
# Total moves = 8, choose 2 for down = C(8,2) = 28
import math
print('Unique paths 3x7:', math.comb(3+7-2, 3-1)) # 28
print('Unique paths 3x3:', math.comb(3+3-2, 3-1)) # 6
print('Unique paths 2x2:', math.comb(2+2-2, 2-1)) # 2Tabella DP 2D per i percorsi unici
Si definisca dp[i][j] come il numero di percorsi che portano alla cella (i,j). La prima riga e la prima colonna contengono solo 1 (c'è un solo modo per raggiungere ogni cella della riga superiore o della colonna più a sinistra). Per le altre celle: dp[i][j] = dp[i-1][j] + dp[i][j-1]. Si riempia la tabella riga per riga; la risposta è dp[m-1][n-1]. Complessità temporale: O(m×n); spazio: O(m×n), riducibile a O(n).
def unique_paths(m, n):
dp = [[1] * n for _ in range(m)]
# First row and column stay as 1s (base cases)
for i in range(1, m):
for j in range(1, n):
dp[i][j] = dp[i-1][j] + dp[i][j-1]
return dp[m-1][n-1]
print(unique_paths(3, 7)) # 28
print(unique_paths(3, 3)) # 6
print(unique_paths(1, 1)) # 1 (already at destination)Ottimizzazione dello spazio a O(n)
Poiché dp[i][j] dipende solo dalla riga corrente e da quella precedente, è possibile sostituire l'intera tabella 2D con un singolo array 1D. Si inizializzino tutti i valori a 1, quindi, per ogni riga, li si aggiorni sul posto: dp[j] += dp[j-1]. Dopo aver elaborato la riga i, dp[j] contiene il valore che nella tabella 2D era dp[i][j]. Questo è un comune schema di ottimizzazione per i problemi di DP 2D.
def unique_paths_1d(m, n):
dp = [1] * n # initial row: all 1s
for i in range(1, m):
for j in range(1, n):
dp[j] += dp[j-1] # dp[j] was dp[i-1][j], dp[j-1] is dp[i][j-1]
return dp[n-1]
print(unique_paths_1d(3, 7)) # 28
print(unique_paths_1d(3, 3)) # 6
# Or use math for O(1)
import math
print(math.comb(3+7-2, 3-1)) # 28Unique Paths II: ostacoli
Unique Paths II (LeetCode 63) aggiunge ostacoli (celle contrassegnate con 1) alla griglia. Qualsiasi percorso che attraversi un ostacolo non è valido, quindi dp[i][j] = 0 se obstacle[i][j] == 1. Altrimenti, la ricorrenza rimane la stessa: dp[i][j] = dp[i-1][j] + dp[i][j-1]. Se la partenza o la destinazione è bloccata, il risultato è immediatamente 0. Si inizializzino con attenzione i casi base: una volta incontrato un 1 nella prima riga o nella prima colonna, tutte le celle successive di quella riga o colonna valgono 0.
def unique_paths_with_obstacles(obstacle_grid):
m, n = len(obstacle_grid), len(obstacle_grid[0])
dp = [[0] * n for _ in range(m)]
# First row
for j in range(n):
if obstacle_grid[0][j] == 1: break
dp[0][j] = 1
# First column
for i in range(m):
if obstacle_grid[i][0] == 1: break
dp[i][0] = 1
for i in range(1, m):
for j in range(1, n):
if obstacle_grid[i][j] == 0:
dp[i][j] = dp[i-1][j] + dp[i][j-1]
return dp[m-1][n-1]
grid = [[0,0,0],[0,1,0],[0,0,0]]
print(unique_paths_with_obstacles(grid)) # 2Problema della somma minima del percorso
Minimum Path Sum (LeetCode 64) chiede: data una griglia m×n contenente interi non negativi, si trovi il percorso dall'angolo in alto a sinistra a quello in basso a destra che minimizza la somma di tutti i numeri lungo il percorso (muovendosi solo a destra o verso il basso). Ad esempio, nella griglia [[1,3,1],[1,5,1],[4,2,1]], il percorso 1→3→1→1→1 dà come somma 7. Lo stato DP è lo stesso dei percorsi unici, ma ora la ricorrenza usa il minimo invece della somma.
grid = [[1, 3, 1],
[1, 5, 1],
[4, 2, 1]]
# Optimal path: (0,0)→(0,1)→(0,2)→(1,2)→(2,2)
# Values: 1 + 3 + 1 + 1 + 1 = 7
print('Expected minimum path sum:', 7)Implementazione DP della somma minima del percorso
Si definisca dp[i][j] come il costo minimo per raggiungere la cella (i,j). Caso base: dp[0][0] = grid[0][0]. Prima riga: dp[0][j] = dp[0][j-1] + grid[0][j] (l'unico percorso possibile proviene da sinistra). Prima colonna: dp[i][0] = dp[i-1][0] + grid[i][0] (l'unico percorso possibile proviene dall'alto). Caso generale: dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1]). Questa è una traduzione diretta del principio di ottimalità.
def min_path_sum(grid):
m, n = len(grid), len(grid[0])
dp = [[0]*n for _ in range(m)]
dp[0][0] = grid[0][0]
for j in range(1, n): # first row
dp[0][j] = dp[0][j-1] + grid[0][j]
for i in range(1, m): # first column
dp[i][0] = dp[i-1][0] + grid[i][0]
for i in range(1, m):
for j in range(1, n):
dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1])
return dp[m-1][n-1]
grid = [[1,3,1],[1,5,1],[4,2,1]]
print(min_path_sum(grid)) # 7Somma minima del percorso in place
Se è consentito modificare la griglia di input, è possibile aggiornarla sul posto per evitare di allocare una tabella DP separata. In questo modo lo spazio ausiliario si riduce a O(1) (oltre allo spazio dell'input). Nei colloqui può capitare che venga chiesta questa ottimizzazione: si chiarisca se è consentito modificare l'input prima di procedere. In caso contrario, il trucco dell'array scorrevole 1D consente di usare spazio O(n) senza modificare l'input.
def min_path_sum_inplace(grid):
m, n = len(grid), len(grid[0])
# Mutate in place
for i in range(m):
for j in range(n):
if i == 0 and j == 0: continue
if i == 0:
grid[i][j] += grid[i][j-1]
elif j == 0:
grid[i][j] += grid[i-1][j]
else:
grid[i][j] += min(grid[i-1][j], grid[i][j-1])
return grid[m-1][n-1]
import copy
grid = [[1,3,1],[1,5,1],[4,2,1]]
print(min_path_sum_inplace(copy.deepcopy(grid))) # 7Somma minima del percorso nel triangolo
Triangle (LeetCode 120) chiede la somma minima del percorso dalla cima alla base di un array a forma di triangolo, dove ogni passo porta a un numero adiacente nella riga sottostante. La DP dal basso verso l'alto è la soluzione più semplice: si parte dalla penultima riga e, per ogni cella, si aggiunge il minimo dei due valori direttamente sottostanti. In questo modo non è necessario tenere traccia degli indici di partenza e la risposta risale naturalmente fino all'apice.
def minimum_total(triangle):
# Bottom-up: start from second-to-last row
dp = triangle[-1][:] # copy of bottom row
for row in range(len(triangle) - 2, -1, -1):
for col in range(len(triangle[row])):
dp[col] = triangle[row][col] + min(dp[col], dp[col+1])
return dp[0]
triangle = [
[2],
[3, 4],
[6, 5, 7],
[4, 1, 8, 3]
]
print(minimum_total(triangle)) # 11 (2+3+5+1)DP su griglia in un dungeon
Dungeon Game (LeetCode 174) chiede la salute iniziale minima necessaria per salvare una principessa nell'angolo in basso a destra di una griglia con celle negative (danno) e positive (guarigione). È necessario muoversi a destra o verso il basso. Il trucco consiste nel riempire la tabella DP a ritroso (dall'angolo in basso a destra a quello in alto a sinistra), calcolando la salute minima necessaria in ogni cella. Per ogni cella: dp[i][j] = max(1, min(dp[i+1][j], dp[i][j+1]) - dungeon[i][j]). La salute deve rimanere sempre almeno pari a 1.
def calculate_minimum_hp(dungeon):
m, n = len(dungeon), len(dungeon[0])
dp = [[0]*n for _ in range(m)]
# Fill from bottom-right
dp[m-1][n-1] = max(1, 1 - dungeon[m-1][n-1])
for i in range(m-2, -1, -1): # last column
dp[i][n-1] = max(1, dp[i+1][n-1] - dungeon[i][n-1])
for j in range(n-2, -1, -1): # last row
dp[m-1][j] = max(1, dp[m-1][j+1] - dungeon[m-1][j])
for i in range(m-2, -1, -1):
for j in range(n-2, -1, -1):
need = min(dp[i+1][j], dp[i][j+1])
dp[i][j] = max(1, need - dungeon[i][j])
return dp[0][0]
dungeon = [[-2,-3,3],[-5,-10,1],[10,30,-5]]
print(calculate_minimum_hp(dungeon)) # 7Confronto tra problemi di DP su griglia
I problemi di DP su griglia condividono la stessa struttura, ma differiscono per la direzione di riempimento e per l'operazione di transizione: Unique Paths usa la somma (conta tutti i modi). Min Path Sum usa il minimo (ottimizza). Dungeon Game si riempie a ritroso (calcola la salute necessaria in base al futuro). Quando si affronta un nuovo problema di DP su griglia, ci si chieda: (1) che cosa rappresenta ogni cella? (2) in quale direzione devo riempire la tabella? (3) quale operazione combina i valori vicini? Le risposte a queste tre domande rivelano l'intera soluzione.
# Summary: Grid DP Patterns
#
# Problem Fill Dir Transition
# Unique Paths top-left dp[i][j] = dp[i-1][j] + dp[i][j-1]
# Unique Paths II top-left same but 0 if obstacle
# Min Path Sum top-left dp[i][j] = grid[i][j] + min(above, left)
# Triangle bottom-up dp[col] = row[col] + min(dp[col], dp[col+1])
# Dungeon bottom-right max(1, min(right, down) - cell)
# Recognise the pattern, write the transition, verify with examples
print('Grid DP summary complete')Riepilogo della complessità della DP su griglia
Tutti i problemi di DP su griglia presentati qui richiedono tempo O(m×n). Lo spazio varia da O(m×n) per una tabella completa fino a O(n) con un array scorrevole 1D, e a O(1) di spazio ausiliario quando la griglia può essere modificata sul posto. Nei colloqui, si menzioni l'ottimizzazione a spazio O(n) dopo aver presentato la soluzione O(m×n): dimostra la consapevolezza dei compromessi. Per tutti i problemi, si valuti anche l'eventuale esistenza di una scorciatoia greedy (come la formula matematica per i percorsi unici).
# O(n) space version of Min Path Sum
def min_path_sum_1d(grid):
m, n = len(grid), len(grid[0])
dp = [float('inf')] * n
dp[0] = 0
for i in range(m):
dp[0] += grid[i][0] # first column: only from above
for j in range(1, n):
dp[j] = grid[i][j] + min(dp[j], dp[j-1])
return dp[n-1]
grid = [[1,3,1],[1,5,1],[4,2,1]]
print(min_path_sum_1d(grid)) # 7Verifica rapida
Verifichi la Sua comprensione dei concetti di Data Structures & Algorithms — Coding Interview Prep presentati in questa lezione.
Riepilogo della lezione
In questa lezione ha imparato che: Unique Paths riempie una tabella 2D con dp[i][j] = dp[i-1][j] + dp[i][j-1] e può essere calcolato in O(1) usando la combinatoria, Min Path Sum usa la stessa struttura, ma sostituisce la somma con min per ottenere il costo ottimale del percorso e tutti i problemi di DP su griglia condividono lo schema di definire uno stato per ogni cella e scegliere un operatore di transizione (somma, minimo, massimo). Ora passeremo alla sottosequenza comune più lunga usando la DP 2D su due sequenze.
Domande Frequenti
La lezione «Percorsi unici e somma minima dei percorsi nelle griglie» è gratuita?
Sì — il testo completo di «Percorsi unici e somma minima dei percorsi nelle griglie» è 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 «Percorsi unici e somma minima dei percorsi nelle griglie»?
Compili una tabella DP 2D per i percorsi unici, con e senza ostacoli, poi la adatti per minimizzare la somma dei valori lungo un percorso 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 1 di 4.
Quanto tempo richiede la lezione «Percorsi unici e somma minima dei percorsi nelle griglie»?
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
- Percorsi unici e somma minima dei percorsi nelle griglie
- Sottosequenza comune più lunga
- Distanza di modifica (Levenshtein)
- Ottimizzazione dello spazio per la DP 2D