-
Notifications
You must be signed in to change notification settings - Fork 0
62. Unique Paths #35
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?
62. Unique Paths #35
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,118 @@ | ||
| # 62. Unique Paths | ||
| - 問題: https://leetcode.com/problems/unique-paths/ | ||
| - 言語: Python | ||
|
|
||
| ## Step1 | ||
| - DPではあるかなと思ったが、単純に組合せ数を求めるだけで良いと考えた | ||
| - 右に $n-1$ 回、下に $m-1$ 回だけ移動する | ||
| - 移動回数の合計 $(n-1)+(m-1)$ から右移動(または下移動)の回数分だけ選ぶ場合の数(組合せの総数、二項係数)がユニークな経路数である | ||
|
|
||
| ### ACした解答 | ||
| ```py | ||
| import math | ||
| class Solution: | ||
| def uniquePaths(self, m: int, n: int) -> int: | ||
| return math.comb((m-1)+(n-1), m-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. こちらのコメントをご参照ください。 |
||
| ``` | ||
| - 所要時間: 13:17 | ||
| - 多分、問題の意図したところとしてはmathライブラリを使わずに自前実装でfactorialを作ってcombinationを求める解答かも? | ||
| - 処理系CPythonでの `math.comb()` の実装: https://github.com/python/cpython/blob/17d5b9df10f53ae3c09c8b22f27d25d9e83b4b7e/Modules/mathmodule.c#L3805 | ||
|
|
||
| ## Step2 | ||
| - 典型コメント集: https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.brtd7l7oqr0f | ||
|
|
||
| - https://github.com/olsen-blue/Arai60/pull/33 | ||
| - Python | ||
| - 1次元配列、2次元配列でDP、再帰、自前実装で二項係数を求める方法 | ||
|
|
||
| ### 他の正答 | ||
| #### 2次元DP | ||
| ```py | ||
| class Solution: | ||
| def uniquePaths(self, m: int, n: int) -> int: | ||
| num_of_paths = [[0] * n for _ in range(m)] | ||
| for c in range(n): | ||
| num_of_paths[0][c] = 1 | ||
| for r in range(m): | ||
| num_of_paths[r][0] = 1 | ||
|
|
||
| for r in range(1, m): | ||
| for c in range(1, n): | ||
| num_of_paths[r][c] = num_of_path[r-1][c] + num_of_path[r][c-1] | ||
|
|
||
| return num_of_path[-1][-1] | ||
| ``` | ||
| - 上端、左端の値を1で初期化 | ||
| - 位置 (r,c) に到達する経路数は下記の和 | ||
| - 上の (r-1, c) から下に移動してくる | ||
| - 左の (r, c-1) から右に移動してくる | ||
| - 時間計算量: $O(n * m)$ | ||
|
|
||
| #### 1次元DP | ||
| ```py | ||
| class Solution: | ||
| def uniquePaths(self, m: int, n: int) -> int: | ||
| num_of_paths = [0] * n | ||
| num_of_paths[0] = 1 | ||
| for _ in range(m): | ||
| for c in range(1, n): | ||
| num_of_paths[c] += num_of_paths[c-1] | ||
|
|
||
| return num_of_paths[-1] | ||
| ``` | ||
| - 時間計算量: $O(n * m)$ | ||
|
|
||
| #### 階乗(自前実装)による二項係数 | ||
| ```py | ||
| class Solution: | ||
| def uniquePaths(self, m: int, n: int) -> int: | ||
| def factorial(k: int) -> int: | ||
| if k == 0: | ||
| return 1 | ||
| return k * factorial(k-1) | ||
| combination = factorial(m + n - 2) // (factorial(m-1) * factorial(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. 演算子周りの空白があるところとないところが混在しているので、統一したほうが良いと思います。
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. ご指摘ありがとうございます。確かにそうですね。 |
||
| return combination | ||
| ``` | ||
|
|
||
| #### 再帰 | ||
| ```py | ||
| 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) | ||
| ``` | ||
| - 時間計算量: `O((n+m-2)C(m-1))` | ||
|
|
||
| - https://github.com/saagchicken/coding_practice/pull/19 | ||
| - Python | ||
| - > 常に同じものが複製されるが、複製される物が immutable ならば問題がない、のほうが近い感覚です。 | ||
| - cf. https://github.com/saagchicken/coding_practice/pull/19#discussion_r2007868751 | ||
| - これは気をつけたい | ||
| - > 私は二重ループを、右の添字のほうが内側のループになるように回しますが、Python の場合はあまり気にするところではないかもしれません。 | ||
| > C++ とかだと、メモリーの配置と速度にわずかに関係があったりします。 | ||
| - cf. https://github.com/saagchicken/coding_practice/pull/19#discussion_r2007873330 | ||
| - 読みやすさの観点以外で添え字の位置まであまり気にしたことはなかった | ||
|
|
||
| ## Step3 | ||
| - 2次元DPで練習 | ||
| ```py | ||
| class Solution: | ||
| def uniquePaths(self, m: int, n: int) -> int: | ||
| num_of_paths = [[0] * n for _ in range(m)] | ||
| for c in range(n): | ||
| num_of_paths[0][c] = 1 | ||
| for r in range(m): | ||
| num_of_paths[r][0] = 1 | ||
|
|
||
| for r in range(1, m): | ||
| for c in range(1, n): | ||
| num_of_paths[r][c] = num_of_paths[r-1][c] + num_of_paths[r][c-1] | ||
|
|
||
| return num_of_paths[-1][-1] | ||
| ``` | ||
| - 所要時間: | ||
| - 1回目: 2:38 | ||
| - 2回目: 2:44 | ||
| - 3回目: 2:20 | ||
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.
実行時間を見積もってみてください。
重要なことは、どれくらいでプログラムが終了するかだと思います。
以下を読んでみてください。
https://nuc.hatenadiary.org/entry/2025/11/29/#%E7%A7%92%E3%81%A7%E3%81%AE%E5%88%A4%E6%96%AD
https://docs.google.com/document/d/11HV35ADPo9QxJOpJQ24FcZvtvioli770WWdZZDaLOfg/edit?tab=t.0#heading=h.1itsm36fdjze
また、実行時間の見積もり方はいくつかあります。
1)公式ドキュメントに記載されている計算量をもとに実行時間を導く
2)Pythonなど実装を読んで計算量を求め、そこから実行時間を導く
3)実際に測ってみる
3つめの手段のは、計算量に関する手がかりがなかったり、実際に試してみないと分からなかったりする場合に使うと思います。それ以外のケースでは、基本的に上の二つの方法で見積もります。
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.
計算量はあくまで手段で、具体的な実行時間を見積もるべきというのは確かにそうですね。
今回の場合だと、入力の最大値が100で、 CPythonの実装で計測し
math.comb(198, 99)は約 1.38 µs でした。