Unique Paths
Robot zaczyna w komórce w lewym górnym rogu siatki o m wierszach i n kolumnach i musi dotrzeć do komórki w prawym dolnym rogu. Każdy ruch przesuwa go o jedną komórkę w prawo lub o jedną komórkę w dół. Zwróć liczbę różnych ścieżek, którymi może się poruszać.
Funkcja
- minteger
- liczba wierszy w siatce
- ninteger
- liczba kolumn w siatce
- Zwracainteger
- liczba różnych ścieżek od komórki w lewym górnym rogu do komórki w prawym dolnym rogu
Ograniczenia
1 ≤ m, n ≤ 100- Wynik jest nie większy niż
2 × 109, więc mieści się w 32-bitowej liczbie całkowitej ze znakiem.
Przykłady
- Wejście
- m = 3n = 4
- Wyjście
- 10
- Wyjaśnienie
- Każda ścieżka obejmuje 2 ruchy w dół i 3 ruchy w prawo, czyli łącznie 5 ruchów. Ścieżkę wyznacza wybór 2 spośród 5 ruchów, które prowadzą w dół, a można je wybrać na 10 sposobów.
- Wejście
- m = 1n = 6
- Wyjście
- 1
- Wyjaśnienie
- Przy jednym wierszu robot może poruszyć się w prawo tylko 5 razy, więc istnieje dokładnie jedna ścieżka.
- Wejście
- m = 4n = 5
- Wyjście
- 35
- Wyjaśnienie
- Każda ścieżka składa się z 3 ruchów w dół i 4 ruchów w prawo. Wybierając, które 3 z 7 ruchów będą prowadzić w dół, otrzymujemy 7 × 6 × 5 / 6 = 35 ścieżek.
+14 ukrytych testów przy wysłaniu
Pytanie dodatkowe
Dla siatki 100 × 100 odpowiedź ma 59 cyfr. Jak zwrócisz ją modulo 10^9+7 za pomocą wzoru, gdy dzielenie przez i już nie działa?
Podpowiedzi
Otwieraj je po kolei. Każda zdradza trochę więcej.
Gdzie mógł znajdować się robot tuż przed wejściem na pole?
Ścieżki prowadzące do komórki to ścieżki prowadzące do komórki powyżej oraz ścieżki prowadzące do komórki po jej lewej stronie. Górny wiersz i lewa kolumna mają dokładnie po jednej ścieżce.
Wpisuj liczby wiersz po wierszu, od lewej do prawej, zachowując jeden wiersz liczb. Możesz też bezpośrednio liczyć kolejności ruchów: ścieżka to wybór, które
m-1spośródm+n-2ruchów prowadzą w dół.
Rozwiązanie
Wypisywanie ścieżek po kolei nie ma sensu: siatka 17 × 17 ma ich już 601,080,390. Trzeba je policzyć bez wypisywania. Ścieżki do komórki to ścieżki do komórki powyżej oraz ścieżki do komórki po jej lewej stronie, co zamienia siatkę w tabelę, którą wypełniasz za jednym razem. Ścieżka to także po prostu kolejność ruchów w dół i w prawo, co daje wzór jawny.
Policz każdą ścieżkę za pomocą rekurencji
Poprawne, ale nie kończy się na największych testach
Intuicja
Pomyśl o ostatnim ruchu robota do komórki w prawym dolnym rogu. Robot dotarł do niej albo z góry, z komórki powyżej, albo z prawej, z komórki po lewej — nigdy z obu kierunków. Zatem ścieżki przez siatkę m × n to ścieżki przez siatkę o jeden wiersz krótszą, uniquePaths(m-1, n), oraz ścieżki przez siatkę o jedną kolumnę węższą, uniquePaths(m, n-1).
Rekurencja kończy się na siatce z jednym wierszem lub jedną kolumną, gdzie robot może poruszać się tylko prosto, więc istnieje dokładnie 1 ścieżka. Każda ścieżka kończy się jednym z dwóch ruchów, dlatego każda jest liczona raz, a suma jest poprawna.
Działa to wolno, ponieważ każda ścieżka kończy się przypadkiem bazowym, który zwraca 1, więc liczba wywołań jest co najmniej równa samej odpowiedzi. Siatka 17 × 17 wymaga ponad 600 milionów wywołań, a testy sięgają odpowiedzi bliskich 1.6 × 10^9. Te same mniejsze siatki są obliczane wiele razy: do (m-1, n-1) dociera się raz z każdego z dwóch węzłów nadrzędnych, a liczba powtórzeń rośnie w miarę schodzenia w dół.
Algorytm
- Jeśli
mlubnwynosi 1, zwróć 1: jedyna ścieżka to linia prosta. - W przeciwnym razie policz ścieżki, których ostatni ruch prowadzi w dół:
uniquePaths(m-1, n). - Policz ścieżki, których ostatni ruch prowadzi w prawo:
uniquePaths(m, n-1). - Zwróć ich sumę.
def uniquePaths(m, n):
# One row or one column: the only path is a straight line
if m == 1 or n == 1:
return 1
# The last move came down from the row above or right from the column before
return uniquePaths(m - 1, n) + uniquePaths(m, n - 1)Wypełniaj siatkę wiersz po wierszu
Intuicja
Rekurencja wielokrotnie pyta o te same komórki, a jest ich tylko m × n. Oblicz liczbę ścieżek do każdej komórki tylko raz, w takiej kolejności, by komórki, których potrzebujesz, były już zawsze gotowe.
Stan: paths[r][c] to liczba ścieżek od komórki w lewym górnym rogu do wiersza r, kolumny c. Rekurencja: paths[r][c] = paths[r-1][c] + paths[r][c-1], czyli liczba ścieżek prowadzących z góry plus liczba ścieżek prowadzących z lewej. Przypadki bazowe: każda komórka w górnym wierszu i w lewej kolumnie ma 1 ścieżkę — prostą linię. Kolejność: wiersz po wierszu, od lewej do prawej, tak aby komórka powyżej i komórka po lewej były wypełnione, zanim będą potrzebne.
Dla m = 3 i n = 4 wiersze to 1 1 1 1, następnie 1 2 3 4, a potem 1 3 6 10. Odpowiedzią jest ostatnia komórka: 10.
Przyjrzyj się teraz temu, co odczytuje proces wypełniania: tylko wiersz powyżej i wiersz, który wypełniasz. Wystarczy więc przechowywać jeden wiersz. Zanim zaktualizujesz row[c], nadal zawiera on liczbę z wiersza powyżej, a row[c-1] zawiera już nową liczbę po lewej, więc row[c] += row[c-1] stanowi całą rekurencję. Czas działania pozostaje równy O(m × n), a zużycie pamięci spada z O(m × n) do O(n).
Algorytm
- Utwórz
rowznelementami, wszystkie równe 1: to górny wiersz. - Powtórz
m-1razy, po jednym razie dla każdego wiersza poniżej górnego. - W każdym wierszu, dla
cod 1 don-1, dodajrow[c-1]dorow[c].row[0]pozostaje równe 1: to lewa kolumna. - Zwróć
row[n-1].
def uniquePaths(m, n):
# row[c] counts the paths into column c of the current row.
# The top row is all 1s: the only way along it is straight right.
row = [1] * n
for _ in range(m - 1):
for c in range(1, n):
# Paths from above (the old row[c]) plus paths from the left (the new row[c-1])
row[c] += row[c - 1]
return row[n - 1]Policz liczbę ruchów za pomocą współczynnika dwumianowego
Intuicja
Każda ścieżka składa się z dokładnie m-1 ruchów w dół i n-1 ruchów w prawo, czyli łącznie m+n-2 ruchów w pewnej kolejności. Każda kolejność tworzy poprawną ścieżkę: robot nigdy nie wykonuje więcej niż m-1 ruchów w dół ani n-1 ruchów w prawo, więc nigdy nie opuszcza siatki. Ścieżka jest zatem równoważna wyborowi, które m-1 spośród m+n-2 ruchów prowadzą w dół, a odpowiedzią jest współczynnik dwumianowy C(m+n-2, m-1).
Tabela z poprzedniego podejścia to trójkąt Pascala obrócony na bok, dlatego oba podejścia dają ten sam wynik. Aby obliczyć współczynnik bez ogromnych silni, buduj go po jednym czynniku naraz. Przy N = m+n-2 i k = min(m, n)-1 pomnóż przez N-k+i, a następnie podziel przez i, dla i od 1 do k. Po kroku i bieżąca wartość wynosi C(N-k+i, i), czyli jest liczbą całkowitą, więc każde dzielenie jest dokładne.
Dla m = 3 i n = 4: N = 5, k = 2, a wartość zmienia się następująco: 1 × 4 / 1 = 4, a następnie 4 × 5 / 2 = 10. Wybór krótszego boku sprawia, że pętla wykonuje najwyżej 99 kroków. Iloczyn przed ostatnim dzieleniem wynosi k razy tyle, co odpowiedź. Dla siatki 17 × 17 jest to 16 × 601,080,390, czyli około 9.6 × 10^9 — wartość przekraczająca zakres 32-bitowy, więc przechowuj ją w 64-bitowej liczbie całkowitej.
Algorytm
- Ustaw
N = m+n-2, liczbę ruchów, orazk = min(m, n)-1. - Rozpocznij 64-bitowe zliczanie od 1.
- Dla
iod 1 dokpomnóż wynik przezN-k+i, a następnie podziel go przezi. - Zwróć wynik.
def uniquePaths(m, n):
# A path is m+n-2 moves; count the ways to choose which of them go down.
# Choose along the shorter side so the loop stays short.
moves = m + n - 2
k = min(m, n) - 1
count = 1
for i in range(1, k + 1):
# count goes from C(moves-k+i-1, i-1) to C(moves-k+i, i); the division is exact
count = count * (moves - k + i) // i
return count
Pułapki i przypadki brzegowe
Zliczanie jest krótkie, więc błędy kryją się na brzegach siatki i w rozmiarze liczb.
- Obliczanie
(m+n-2)!i dzielenie przez pozostałe dwie silnie prowadzi do przepełnienia na długo przed przepełnieniem wyniku: 21! wykracza już poza zakres 64-bitowy, am+n-2osiąga 105 w siatce 100 × 7. - Dzielenie przed mnożeniem, jak w
count / i * (N-k+i), powoduje obcięcie, ponieważcountnie zawsze jest wielokrotnościąi. Najpierw mnoż: iloczyn zawsze dzieli się bez reszty. - Iloczyn
count × (N-k+i)może przekroczyć 2^31, nawet jeśli wynik tego nie robi. Przechowuj go w 64-bitowej liczbie całkowitej. - Pozostawienie górnego wiersza lub lewej kolumny z wartością 0 zamiast 1 sprawia, że każda komórka ma wartość 0. Siatka z jednym wierszem lub jedną kolumną ma dokładnie 1 ścieżkę.
- Zamiana miejscami wierszy i kolumn nie zmienia wyniku, ponieważ
C(m+n-2, m-1) = C(m+n-2, n-1).
Najczęstsze pytania4
Jaki jest wzór na unikalne ścieżki?
Odpowiedzią jest współczynnik dwumianowy C(m+n-2, m-1). Każda ścieżka składa się z m-1 ruchów w dół i n-1 ruchów w prawo w pewnej kolejności, a wybór, które z m+n-2 ruchów są w dół, wyznacza ścieżkę. Dla siatki 3 × 4 jest to C(5, 2) = 10.
Jaka jest złożoność czasowa problemu Unique Paths?
Tablica programowania dynamicznego wymaga czasu O(m × n) i pamięci O(n), gdy przechowujesz jeden wiersz. Wzór dwumianowy wymaga czasu O(min(m, n)) i pamięci O(1). Zwykła rekurencja wykonuje co najmniej tyle wywołań, ile jest ścieżek, co oznacza złożoność wykładniczą względem m + n.
Jak rozwiązać problem unikalnych ścieżek, gdy niektóre komórki są zablokowane?
Użyj tej samej tabeli i ustaw liczbę ścieżek w zablokowanej komórce na 0, aby żadna ścieżka przez nią nie prowadziła. Górny wiersz i lewa kolumna przestają składać się wyłącznie z jedynek: każda komórka za zablokowaną komórką w górnym wierszu ma 0 ścieżek. Wzór już nie działa, ponieważ zakłada, że dozwolona jest każda kolejność ruchów.
Dlaczego tabela unikalnych ścieżek odpowiada trójkątowi Pascala?
Każda komórka dodaje wartość komórki powyżej i komórki po lewej stronie, zgodnie z regułą budującą trójkąt Pascala, odczytywany wzdłuż jego przekątnych. Komórka w wierszu r i kolumnie c zawiera C(r+c, r), więc komórka w prawym dolnym rogu zawiera C(m+n-2, m-1).
Podobne zadania
Zadania oparte na tych samych pomysłach. Rozwiązanie dwóch lub trzech utrwala schemat.
Python
def uniquePaths(m, n):
# Napisz kod tutajPrzypadek 1
Przypadek 2
Przypadek 3
Wejście
m = 3 n = 4
Oczekiwane
10