Unikalne ścieżki i minimalna suma ścieżki na siatkach
Wypełnią Państwo dwuwymiarową tablicę DP dla unikalnych ścieżek, z przeszkodami i bez nich, a następnie dostosują ją do minimalizowania sumy wartości na ścieżce.
Unikalne ścieżki i minimalna suma ścieżki na siatkach to bezpłatna lekcja DSA Interview Prep na CoddyKit. To lekcja 1 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 DSA Interview Prep, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs DSA Interview Prep zawiera 4 lekcji w sumie.
Unique Paths w siatce
Unique Paths (LeetCode 62) pyta: w siatce m×n ile jest różnych ścieżek z lewego górnego do prawego dolnego rogu, jeśli można poruszać się tylko w prawo lub dół? Dla siatki 3×7 odpowiedzią jest 28. Kluczowa obserwacja jest taka, że każda ścieżka do komórki (i,j) musi prowadzić z (i-1,j) (z góry) albo z (i,j-1) (z lewej), co prowadzi do naturalnego sformułowania 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)) # 2Tabela DP 2D dla Unique Paths
Zdefiniuj dp[i][j] jako liczbę ścieżek do komórki (i,j). Pierwszy wiersz i pierwsza kolumna składają się wyłącznie z jedynek, ponieważ do każdej komórki w górnym wierszu lub skrajnej lewej kolumnie prowadzi tylko jedna ścieżka. Dla pozostałych komórek: dp[i][j] = dp[i-1][j] + dp[i][j-1]. Tabelę należy wypełniać wiersz po wierszu, a odpowiedzią jest dp[m-1][n-1]. Złożoność czasowa: O(m×n), pamięciowa: O(m×n), którą można zmniejszyć do 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)Optymalizacja pamięci do O(n)
Ponieważ dp[i][j] zależy tylko od bieżącego i poprzedniego wiersza, pełną tabelę 2D można zastąpić pojedynczą tablicą 1D. Należy zainicjalizować wszystkie wartości jako 1, a następnie dla każdego wiersza aktualizować je w miejscu: dp[j] += dp[j-1]. Po przetworzeniu wiersza i wartość dp[j] odpowiada wartości dp[i][j] w tabeli 2D. Jest to często stosowany schemat optymalizacji w problemach 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: przeszkody
Unique Paths II (LeetCode 63) dodaje do siatki przeszkody (komórki oznaczone wartością 1). Każda ścieżka przechodząca przez przeszkodę jest niepoprawna, dlatego dp[i][j] = 0, jeśli obstacle[i][j] == 1. W przeciwnym razie rekurencja pozostaje taka sama: dp[i][j] = dp[i-1][j] + dp[i][j-1]. Zablokowany początek lub koniec od razu daje wynik 0. Należy starannie zainicjalizować przypadki bazowe — gdy w pierwszym wierszu lub kolumnie pojawi się 1, wszystkie kolejne komórki w tym wierszu lub tej kolumnie mają wartość 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)) # 2Problem minimalnej sumy ścieżki
Minimum Path Sum (LeetCode 64) pyta: dla siatki m×n wypełnionej nieujemnymi liczbami należy znaleźć ścieżkę z lewego górnego do prawego dolnego rogu, która minimalizuje sumę wszystkich liczb na ścieżce (poruszając się tylko w prawo lub w dół). Na przykład w przypadku [[1,3,1],[1,5,1],[4,2,1]] ścieżka 1→3→1→1→1 daje sumę 7. Stan DP jest taki sam jak w przypadku Unique Paths, ale w rekurencji dodawanie zastępuje się operacją minimum.
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)Implementacja DP dla Min Path Sum
Zdefiniuj dp[i][j] jako minimalny koszt dotarcia do komórki (i,j). Przypadek bazowy: dp[0][0] = grid[0][0]. Pierwszy wiersz: dp[0][j] = dp[0][j-1] + grid[0][j] (jedyna droga prowadzi z lewej). Pierwsza kolumna: dp[i][0] = dp[i-1][0] + grid[i][0] (jedyna droga prowadzi z góry). Przypadek ogólny: dp[i][j] = grid[i][j] + min(dp[i-1][j], dp[i][j-1]). Jest to bezpośrednie zastosowanie zasady optymalności.
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)) # 7Min Path Sum w miejscu
Jeśli można modyfikować siatkę wejściową, można aktualizować ją w miejscu, aby uniknąć przydzielania osobnej tabeli DP. Zmniejsza to pamięć dodatkową do O(1) (nie licząc danych wejściowych). Podczas rozmowy kwalifikacyjnej może paść pytanie o tę optymalizację — przed jej zastosowaniem należy ustalić, czy modyfikowanie danych wejściowych jest dozwolone. Jeśli nie, sztuczka z jednowymiarową tablicą kroczącą pozwala uzyskać pamięć O(n) bez modyfikowania danych wejściowych.
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))) # 7Minimalna suma ścieżki w trójkącie
Triangle (LeetCode 120) wymaga znalezienia minimalnej sumy ścieżki od góry do dołu w tablicy reprezentującej trójkąt, gdzie każdy krok prowadzi do sąsiedniej liczby w poniższym wierszu. Najczytelniejsze jest DP od dołu: należy rozpocząć od przedostatniego wiersza i dla każdej komórki dodać minimum z dwóch komórek znajdujących się bezpośrednio poniżej. Dzięki temu nie trzeba śledzić indeksów początkowych, a odpowiedź naturalnie wędruje do wierzchołka.
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 na siatce lochu
Dungeon Game (LeetCode 174) pyta o minimalne początkowe zdrowie potrzebne do uratowania księżniczki znajdującej się w prawym dolnym rogu siatki zawierającej komórki z wartościami ujemnymi (obrażenia) i dodatnimi (leczenie). Należy poruszać się w prawo lub w dół. Sztuczka polega na wypełnieniu tabeli DP od końca (od prawego dolnego do lewego górnego rogu) i obliczeniu minimalnej liczby punktów zdrowia potrzebnych w każdej komórce. Dla każdej komórki: dp[i][j] = max(1, min(dp[i+1][j], dp[i][j+1]) - dungeon[i][j]). Zdrowie musi zawsze wynosić co najmniej 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)) # 7Porównanie problemów DP na siatkach
Problemy DP na siatkach mają tę samą strukturę, ale różnią się kierunkiem wypełniania i operacją przejścia: Unique Paths używa dodawania (zlicza wszystkie sposoby). Min Path Sum używa minimum (optymalizuje koszt). Dungeon Game wypełnia tabelę od końca (oblicza zdrowie potrzebne w przyszłości). Podczas rozwiązywania nowego problemu DP na siatce należy zadać sobie trzy pytania: (1) Co reprezentuje każda komórka? (2) W jakim kierunku należy wypełniać tabelę? (3) Jaka operacja łączy wartości sąsiednich komórek? Odpowiedzi na te trzy pytania ujawniają całe rozwiązanie.
# 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')Podsumowanie złożoności DP na siatkach
Wszystkie omówione problemy DP na siatkach mają złożoność czasową O(m×n). Zużycie pamięci wynosi od O(m×n) dla pełnej tabeli, przez O(n) przy użyciu jednowymiarowej tablicy kroczącej, aż po O(1) pamięci dodatkowej, gdy siatkę można modyfikować w miejscu. Podczas rozmowy kwalifikacyjnej należy wspomnieć o optymalizacji pamięci do O(n) po przedstawieniu rozwiązania O(m×n) — pokazuje to świadomość kompromisów. W przypadku wszystkich problemów warto również rozważyć, czy istnieje proste rozwiązanie zachłanne (na przykład wzór matematyczny dla Unique Paths).
# 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)) # 7Szybki test
Sprawdź swoją wiedzę na temat zagadnień Data Structures & Algorithms — Coding Interview Prep omawianych w tej lekcji.
Podsumowanie lekcji
W tej lekcji nauczyli się Państwo, że: Unique Paths wypełnia tabelę 2D za pomocą dp[i][j] = dp[i-1][j] + dp[i][j-1] i można obliczyć ten problem w czasie O(1) przy użyciu kombinatoryki, Min Path Sum wykorzystuje tę samą strukturę, ale zastępuje dodawanie operacją min w celu znalezienia optymalnego kosztu ścieżki oraz wszystkie problemy DP na siatkach mają wspólny schemat: zdefiniowanie stanu dla każdej komórki i wybór operatora przejścia (sum, min, max). Następnie omówimy Longest Common Subsequence, używając DP 2D dla dwóch sekwencji.
Często zadawane pytania
Czy lekcja „Unikalne ścieżki i minimalna suma ścieżki na siatkach” jest bezpłatna?
Tak — pełny tekst „Unikalne ścieżki i minimalna suma ścieżki na siatkach” 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 DSA Interview Prep, przejdź na CoddyKit PRO. Kurs DSA Interview Prep zawiera 4 lekcji w sumie.
Co nauczysz się w „Unikalne ścieżki i minimalna suma ścieżki na siatkach”?
Wypełnią Państwo dwuwymiarową tablicę DP dla unikalnych ścieżek, z przeszkodami i bez nich, a następnie dostosują ją do minimalizowania sumy wartości na ścieżce. Ćwiczysz DSA 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ąć DSA Interview Prep?
Nie wymagamy żadnego doświadczenia. DSA 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 1 z 4.
Ile czasu zajmuje lekcja „Unikalne ścieżki i minimalna suma ścieżki na siatkach”?
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 DSA Interview Prep?
Tak. Każda lekcja DSA 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
- Unikalne ścieżki i minimalna suma ścieżki na siatkach
- Najdłuższy wspólny podciąg
- Odległość edycyjna (Levenshteina)
- Optymalizacja pamięci dla dwuwymiarowego DP