-
Notifications
You must be signed in to change notification settings - Fork 0
62 unique paths medium #18
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| # | ||
| # @lc app=leetcode id=62 lang=python3 | ||
| # | ||
| # [62] Unique Paths | ||
| # | ||
|
|
||
| # @lc code=start | ||
| class Solution: | ||
| def uniquePaths(self, m: int, n: int) -> int: | ||
| num_paths_in_row = [0] * n | ||
| num_paths_in_row[0] = 1 | ||
| for _ in range(m): | ||
| for column in range(1, n): | ||
| num_paths_in_row[column] += num_paths_in_row[column - 1] | ||
| return num_paths_in_row[-1] | ||
|
|
||
|
|
||
| # @lc code=end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| # | ||
| # @lc app=leetcode id=62 lang=python3 | ||
| # | ||
| # [62] Unique Paths | ||
| # | ||
|
|
||
| # @lc code=start | ||
| class Solution: | ||
| def uniquePaths(self, m: int, n: int) -> int: | ||
| num_paths = [[0] * n for _ in range(m)] | ||
| for row in range(m): | ||
| for column in range(n): | ||
| if row == 0 or column == 0: | ||
| num_paths[row][column] = 1 | ||
| continue | ||
| num_paths[row][column] = num_paths[row - 1][column] + num_paths[row][column - 1] | ||
|
|
||
| return num_paths[-1][-1] | ||
|
|
||
|
|
||
| # @lc code=end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,117 @@ | ||
| # 問題へのリンク | ||
| [Unique Paths - LeetCode](https://leetcode.com/problems/unique-paths/description/) | ||
|
|
||
| # 言語 | ||
| Python | ||
|
|
||
| # 問題の概要 | ||
| m x n のグリッドが与えられる。左上のマスから右下のマスまで、右または下にのみ移動する場合のユニークな経路の総数を求める問題である。 | ||
|
|
||
| # 自分の解法 | ||
|
|
||
| この問題は、最終的に `(m-1) + (n-1)` 回の移動のうち、どの `m-1` 回を「下」への移動にするか(残りは「右」になる)を選択する組み合わせの問題として解釈できる。したがって、組み合わせの公式 `C(m+n-2, m-1)` を用いて解くことができる。 | ||
|
|
||
| ## step1 | ||
| `math.comb` を利用して、組み合わせ計算を直接実装した。これは最も簡潔で効率的な解法である。 | ||
|
|
||
| ```python | ||
| import math | ||
|
|
||
|
|
||
| class Solution: | ||
| def uniquePaths(self, m: int, n: int) -> int: | ||
| if m == 1 and n == 1: | ||
| return 1 | ||
| return math.comb(m + n - 2, m - 1) | ||
| ``` | ||
|
|
||
| - 時間計算量:`O(min(m, n))`。`math.comb`の実装に依存するが、効率的に計算される。 | ||
| - 空間計算量:`O(1)`。 | ||
|
|
||
| ## step2 | ||
| `math.comb` に頼らず、組み合わせを計算する関数を自前で実装した。これにより、ライブラリの内部実装への理解を深め、特定のバージョンに依存しないコードとなる。 | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def uniquePaths(self, m: int, n: int) -> int: | ||
| def combination(n: int, k: int) -> int: | ||
| assert 0 <= k <= n | ||
| if n == k == 0: | ||
| return 1 | ||
| if n - k < k: | ||
| k = n - k | ||
| numerator = 1 | ||
| denominator = 1 | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 個人的には処理の直前で変数の用意をした方が好きなので、
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 確かに読み手が目でたどる必要がなくなり、読みやすくなりそうですね。アドバイスありがとうございます。 |
||
| for num in range(n - k + 1, n + 1): | ||
| numerator *= num | ||
| for num in range(1, k + 1): | ||
| denominator *= num | ||
| return numerator // denominator | ||
|
|
||
| moves_to_right = n - 1 | ||
| moves_to_bottom = m - 1 | ||
| total_moves = moves_to_right + moves_to_bottom | ||
| return combination(total_moves, moves_to_bottom) | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. ここは以下のようにして1行コメントを入れたりするのも選択肢としてよさそうかなと感じました。 |
||
| ``` | ||
|
|
||
| - 時間計算量:`O(min(m, n))`。組み合わせの計算 `C(n, k)` のループ回数は `k` に比例するため。 | ||
| - 空間計算量:`O(1)`。 | ||
|
|
||
| # 別解・模範解答 | ||
|
|
||
| ### 1. 動的計画法 (2D DP) | ||
| `dp[i][j]` を `(i, j)` に到達する経路の数と定義する。`dp[i][j] = dp[i-1][j] + dp[i][j-1]` という漸化式で解くことができる。 | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def uniquePaths(self, m: int, n: int) -> int: | ||
| num_paths = [[0] * n for _ in range(m)] | ||
| for row in range(m): | ||
| for column in range(n): | ||
| if row == 0 or column == 0: | ||
| num_paths[row][column] = 1 | ||
| continue | ||
| num_paths[row][column] = num_paths[row - 1][column] + num_paths[row][column - 1] | ||
|
|
||
| return num_paths[-1][-1] | ||
| ``` | ||
|
|
||
| - 時間計算量:`O(m*n)`。グリッドの全マスを一度ずつ計算するため。 | ||
| - 空間計算量:`O(m*n)`。`m x n` のDPテーブルを保持するため。 | ||
|
|
||
| ### 2. 動的計画法 (1D DP) | ||
| 2D DPの空間計算量を最適化したアプローチである。`i`行目の計算は`i-1`行目の情報のみに依存するため、1次元配列を使い回すことで空間を削減できる。 | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def uniquePaths(self, m: int, n: int) -> int: | ||
| num_paths_in_row = [1] * n | ||
| for _ in range(1, m): | ||
| for column in range(1, n): | ||
| num_paths_in_row[column] += num_paths_in_row[column - 1] | ||
| return num_paths_in_row[-1] | ||
| ``` | ||
|
|
||
| - 時間計算量:`O(m*n)`。 | ||
| - 空間計算量:`O(n)`。1行分の情報を保持する配列のみ使用するため。 | ||
|
|
||
| ### 3. 再帰 (メモ化) | ||
| トップダウンの動的計画法アプローチである。`@cache`デコレータ(メモ化)を使い、重複する計算を避ける。 | ||
|
|
||
| ```python | ||
| from functools import cache | ||
|
|
||
|
|
||
| class Solution: | ||
| @cache | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. デコレーターの振る舞いで、インスタンス間でキャッシュが共有されるか、などは確認しておいてください。 class A:
def f():
pass
obj1 = A()
obj2 = A()
print(obj1.f is obj2.f)
print(obj1.f)
print(obj2.f) |
||
| def uniquePaths(self, m: int, n: int) -> int: | ||
| if m == 1 or n == 1: | ||
| return 1 | ||
| return self.uniquePaths(m - 1, n) + self.uniquePaths(m, n - 1) | ||
| ``` | ||
|
|
||
| - 時間計算量:`O(m*n)`。メモ化により、各 `(m, n)` の組み合わせは一度しか計算されないため。 | ||
| - 空間計算量:`O(m*n)`。再帰のスタックとキャッシュのための空間が必要である。 | ||
|
|
||
| # 次に解く問題 | ||
| - [Binary Tree Level Order Traversal - LeetCode](https://leetcode.com/problems/binary-tree-level-order-traversal/) | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,38 @@ | ||
| # | ||
| # @lc app=leetcode id=62 lang=python3 | ||
| # | ||
| # [62] Unique Paths | ||
| # | ||
|
|
||
| # @lc code=start | ||
| from collections import deque | ||
|
|
||
|
|
||
| class Solution: | ||
| def uniquePaths(self, m: int, n: int) -> int: | ||
| top_left = (0, 0) | ||
| num_paths_grid = [[0 for _ in range(n)] for _ in range(m)] | ||
| num_paths_grid[top_left[0]][top_left[1]] = 1 | ||
| queue: deque[tuple[int, int]] = deque([top_left]) | ||
| directions = ((0, 1), (1, 0)) | ||
| visited: set[tuple[int, int]] = set([top_left]) | ||
|
|
||
| def is_valid(row, column) -> bool: | ||
| return 0 <= row < m and 0 <= column < n | ||
|
|
||
| while queue: | ||
| row, column = queue.popleft() | ||
| num_paths = num_paths_grid[row][column] | ||
| neighbors = [(row + drow, column + dcol) for drow, dcol in directions] | ||
| for neighbor_row, neighbor_col in neighbors: | ||
| if not is_valid(neighbor_row, neighbor_col): | ||
| continue | ||
| num_paths_grid[neighbor_row][neighbor_col] += num_paths | ||
| if (neighbor_row, neighbor_col) in visited: | ||
| continue | ||
| visited.add((neighbor_row, neighbor_col)) | ||
| queue.append((neighbor_row, neighbor_col)) | ||
| return num_paths_grid[m - 1][n - 1] | ||
|
|
||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. visited は何を表しているか難しいなと感じました。queue に追加したらvisited に追加していますが、この時点で追加した地点の num_paths_grid の値は確定していないように思います。row + column が増加する順に見ているので問題なく動いていますが、やや不思議な印象を受けました。
Owner
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. コメントありがとうございます。 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 木構造ではないので postorder との関連づけるのは難しそうです。 while queue:
row, column = queue.popleft()
num_paths = num_paths_grid[row][column]
if is_valid(row, column + 1):
num_paths_grid[row][column + 1] += num_paths
queue.append((row, column + 1))
if is_valid(row + 1, column):
num_paths_grid[row + 1][column] += num_paths
if column == 0:
queue.append((row + 1, column))
return num_paths_grid[m - 1][n - 1] |
||
|
|
||
| # @lc code=end | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| # | ||
| # @lc app=leetcode id=62 lang=python3 | ||
| # | ||
| # [62] Unique Paths | ||
| # | ||
|
|
||
| # @lc code=start | ||
| from functools import cache | ||
|
|
||
|
|
||
| class Solution: | ||
| @cache | ||
| def uniquePaths(self, m: int, n: int) -> int: | ||
| if m == 1 or n == 1: | ||
| return 1 | ||
| return self.uniquePaths(m - 1, n) + self.uniquePaths(m, n - 1) | ||
|
|
||
|
|
||
| # @lc code=end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,20 @@ | ||
| # | ||
| # @lc app=leetcode id=62 lang=python3 | ||
| # | ||
| # [62] Unique Paths | ||
| # | ||
|
|
||
| # @lc code=start | ||
|
|
||
|
|
||
| import math | ||
|
|
||
|
|
||
| class Solution: | ||
| def uniquePaths(self, m: int, n: int) -> int: | ||
| if m == 1 and n == 1: | ||
| return 1 | ||
| return math.comb(m + n - 2, m - 1) | ||
|
|
||
|
|
||
| # @lc code=end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,25 @@ | ||
| # | ||
| # @lc app=leetcode id=62 lang=python3 | ||
| # | ||
| # [62] Unique Paths | ||
| # | ||
|
|
||
| # @lc code=start | ||
| class Solution: | ||
| def uniquePaths(self, m: int, n: int) -> int: | ||
| def combination(n: int, k: int) -> int: | ||
| assert 0 <= k <= n | ||
| if n == k == 0: | ||
| return 1 | ||
| numerator = 1 | ||
| denominator = 1 | ||
| for num in range(n - k + 1, n + 1): | ||
| numerator *= num | ||
| for num in range(1, k + 1): | ||
| denominator *= num | ||
| return numerator // denominator | ||
|
|
||
| return combination(n + m - 2, m - 1) | ||
|
|
||
|
|
||
| # @lc code=end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,28 @@ | ||
| # | ||
| # @lc app=leetcode id=62 lang=python3 | ||
| # | ||
| # [62] Unique Paths | ||
| # | ||
|
|
||
| # @lc code=start | ||
| class Solution: | ||
| def uniquePaths(self, m: int, n: int) -> int: | ||
| def combination(n: int, k: int) -> int: | ||
| assert 0 <= k <= n | ||
| if n == k == 0: | ||
| return 1 | ||
| numerator = 1 | ||
| denominator = 1 | ||
| for num in range(n - k + 1, n + 1): | ||
| numerator *= num | ||
| for num in range(1, k + 1): | ||
| denominator *= num | ||
| return numerator // denominator | ||
|
|
||
| vertical_moves = m - 1 | ||
| horizontal_moves = n - 1 | ||
| total_moves = vertical_moves + horizontal_moves | ||
| return combination(total_moves, vertical_moves) | ||
|
|
||
|
|
||
| # @lc code=end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| # | ||
| # @lc app=leetcode id=62 lang=python3 | ||
| # | ||
| # [62] Unique Paths | ||
| # | ||
|
|
||
| # @lc code=start | ||
| class Solution: | ||
| def uniquePaths(self, m: int, n: int) -> int: | ||
| def combination(n: int, k: int) -> int: | ||
| assert 0 <= k <= n | ||
| if n == k == 0: | ||
| return 1 | ||
| if n - k < k: | ||
| k = n - k | ||
| numerator = 1 | ||
| denominator = 1 | ||
| for num in range(n - k + 1, n + 1): | ||
| numerator *= num | ||
| for num in range(1, k + 1): | ||
| denominator *= num | ||
| return numerator // denominator | ||
|
|
||
| moves_to_right = n - 1 | ||
| moves_to_bottom = m - 1 | ||
| total_moves = moves_to_right + moves_to_bottom | ||
| return combination(total_moves, moves_to_bottom) | ||
|
|
||
|
|
||
| # @lc code=end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| # | ||
| # @lc app=leetcode id=62 lang=python3 | ||
| # | ||
| # [62] Unique Paths | ||
| # | ||
|
|
||
| # @lc code=start | ||
| class Solution: | ||
| def uniquePaths(self, m: int, n: int) -> int: | ||
| num_paths_in_row = [0] * n | ||
| num_paths_in_row[0] = 1 | ||
| for _ in range(m): | ||
| for column in range(1, n): | ||
| num_paths_in_row[column] += num_paths_in_row[column - 1] | ||
| return num_paths_in_row[-1] | ||
|
|
||
|
|
||
| # @lc code=end |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| # | ||
| # @lc app=leetcode id=62 lang=python3 | ||
| # | ||
| # [62] Unique Paths | ||
| # | ||
|
|
||
| # @lc code=start | ||
| import math | ||
|
|
||
|
|
||
| class Solution: | ||
| def uniquePaths(self, m: int, n: int) -> int: | ||
| moves_to_bottom = m - 1 | ||
| moves_to_right = n - 1 | ||
| total_moves = moves_to_bottom + moves_to_right | ||
| return math.comb(total_moves, moves_to_bottom) | ||
|
|
||
|
|
||
| # @lc code=end |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
if 文なくても動きますか?
https://docs.python.org/3/library/math.html#math.comb
math.comb(0, 0) = 0! / (0! * 0!) = 1 になりそうですね。
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
レビューありがとうございます。
確かに動きますね!