0Pricing
DSA Interview Prep · Lekcja

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

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

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

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

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

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

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

Szybki 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

  1. Unikalne ścieżki i minimalna suma ścieżki na siatkach
  2. Najdłuższy wspólny podciąg
  3. Odległość edycyjna (Levenshteina)
  4. Optymalizacja pamięci dla dwuwymiarowego DP
← Powrót do DSA Interview Prep