-
Notifications
You must be signed in to change notification settings - Fork 0
62. Unique Paths #26
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 #26
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,145 @@ | ||
| ### Step1 | ||
|
|
||
| - 算数の問題? | ||
| - 小数点のずれが怖いので、最初に全部かけてから後で割った | ||
|
|
||
| ```python | ||
|
|
||
| class Solution: | ||
| def comb(all, choice): | ||
| comb_number = 1 | ||
| for n in range(choice): | ||
| comb_number *= (all - n) | ||
| for n in range(choice): | ||
| comb_number /= (n + 1) | ||
| return comb_number | ||
|
|
||
| def uniquePaths(self, m: int, n: int) -> int: | ||
| down_move_count = m - 1 | ||
| right_move_count = n - 1 | ||
| all_move_count = down_move_count + right_move_count | ||
| return comb(all_move_count, down_move_count) | ||
| ``` | ||
|
|
||
| ## Step2 | ||
|
|
||
| - 再帰を試したらTLE、再帰の回数が多すぎ | ||
| - 呼び出し回数、2^(m+n)かな? | ||
|
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を足しているはずなので、返ってくる値と同じです。 |
||
|
|
||
| ```python | ||
|
|
||
| class Solution: | ||
| 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) | ||
| ``` | ||
|
|
||
| - メモ化してDPでやるしかないかな | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def uniquePaths(self, m: int, n: int) -> int: | ||
| num_path = [[0] * m for _ in range(n)] | ||
|
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. path は 2 個以上ある場合がありますので、複数形にして |
||
| for row in range(n): | ||
| for column in range(m): | ||
| if row == 0 or column == 0: | ||
| num_path[row][column] = 1 | ||
| continue | ||
| num_path[row][column] = num_path[row - 1][column] + num_path[row][column - 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. これはrow == 0 or col == 0でcontinueしてるので大丈夫だと思います |
||
| return num_path[n - 1][m - 1] | ||
| ``` | ||
| - キャッシュデコレータ使えば再帰でもOK | ||
|
|
||
| ```python | ||
|
|
||
| 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) | ||
| ``` | ||
| - メモリ線形バージョン | ||
| - メモリを節約する必要がある実用上の場面、実はよくわかっていない | ||
| - for文の_とiは、ちょっと今回の問題の趣旨だとわかりにくくてやばかったかも | ||
|
|
||
| ```python | ||
|
|
||
| class Solution: | ||
| def uniquePaths(self, m: int, n: int) -> int: | ||
| num_path = [0] * n | ||
|
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 _ in range(m): | ||
| for i in range(n): | ||
| if i == 0: | ||
| num_path[i] = 1 | ||
| continue | ||
| num_path[i] += num_path[i - 1] | ||
| return num_path[n - 1] | ||
| ``` | ||
|
|
||
| - 上のを、以下のを参考にして直す | ||
| - https://github.com/hayashi-ay/leetcode/pull/39/files | ||
| - これも見てみる(https://github.com/SuperHotDogCat/coding-interview/pull/16/files) | ||
| - O(min(m, n))のメモリにできますね、なるほど | ||
| - 個人的には、初期化はall zeroで、all oneの配列をfor文の中で作りたいかも | ||
| - 上の二次元DPの処理とも一致する | ||
| - 今までのコード、最後に配列を返すとき[-1]の方がいいか迷う | ||
| - 1で初期化する手もあったが、なんとなくスッキリしなくて後で1を代入することにした | ||
|
|
||
| ```python | ||
| class Solution: | ||
| def uniquePaths(self, m: int, n: int) -> int: | ||
| if n > m: | ||
| return self.uniquePaths(n, m) | ||
| num_path = [0] * n | ||
| for col in range(m): | ||
| for row in range(n): | ||
| if row == 0: | ||
| num_path[row] = 1 | ||
| continue | ||
| num_path[row] += num_path[row - 1] | ||
| return num_path[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. 末尾の要素を返せばよいため、 |
||
| ``` | ||
|
|
||
| - math.combの利用 | ||
|
|
||
| ```python | ||
|
|
||
| class Solution: | ||
| def uniquePaths(self, m: int, n: int) -> int: | ||
| return comb((m - 1) + (n - 1), n - 1) | ||
| ``` | ||
|
|
||
| mathモジュールの階乗のコードを見る(https://github.com/python/cpython/blob/main/Modules/mathmodule.c) | ||
|
|
||
| おそらく掛け算がビットシフトより遅いのでこういう工夫がされてる? | ||
|
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. そうです。テクニック周りの話がまとまっている記事があるので、参考になりそうです。この例はfactorial関数についてですが、だいたい同じだと思います。
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. これ面白い、速度比較のとこが特に面白かったです、ありがとうございます |
||
|
|
||
| - factorial_partial_product | ||
| - startからstopまでの奇数の階乗の積を、64bitオーバーフローしない範囲まで分割統治で区切ってから計算。(オーバーフローの判定は最大値のbitcountにmultiply_countをかける、logの性質) | ||
| - factorial_odd_part | ||
| - 階乗の奇数パートを計算。innerに、nまでの数の中で2で最大k回割れる数の積の奇数パートを、factorial_partial_productを利用してkが大きい順に保存。それを順にouterにかけていく。 | ||
| - 1, 1, 1*3, 1*3*5、、、みたいにだんだん掛け算範囲が増加して計算されるので、ループごとに前の値を保存して節約 | ||
| - math_factorial | ||
| - factorial_odd_partで奇数パート計算したあと、2で割り切れる数ぶんbit shiftする(これはn//2 + n//4 + ,,, で計算) | ||
|
|
||
| mathモジュールのcombのコード | ||
|
|
||
| - perm_comb_small C(n, k)のnが64bitに収まるとき かつkが1ではないとき | ||
| - 結果が64bitに収まる、かつk ≤ 34 | ||
| - 階乗とその逆数(mod 2^64)について、奇数パートと、2で何回割り切れるかの配列が、ある程度小さいのなら作ってあるので、そこから頑張って計算 | ||
| - 結果がlong longに収まる、かつk≤13 | ||
| - *n/1 *(n-1)/2 *(n-2)/3 を繰り返す(確かにこれなら途中で非整数にならない) | ||
| - その他 | ||
| - C(n, k) = C(n, j) * C(n-j, k-j) // C(k, j) を利用して再帰的に | ||
| - perm_comb nが64bitに収まらないがkは収まる、もしくはk=1の時 | ||
| - kが0→1を返す kが1→nを返す | ||
| - C(n, k) = C(n, j) * C(n-j, k-j) // C(k, j) を利用して再帰的に | ||
| - kはoverflowしてはいけないので、分母のC(k, j)はperm_comb_smallを利用 | ||
| - math_comb_impl 多分本体 条件に従って上のどちらかに渡してるだけだが、以下の操作もしている | ||
| - nとkが負でないかのチェック | ||
| - k = min(n-k, k)にする。そのあとkがoverflowしないかもチェック | ||
| - n < kなら0を返す | ||
|
|
||
| こういうのの実装、見ると楽しいことに気づいたが、あんまりハマりすぎても問題が進まないので程々に | ||
|
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 comment
The reason will be displayed to describe this comment to others. Learn more.
////=演算子を使えば整数で割り算ができると思います。https://docs.python.org/ja/3/reference/lexical_analysis.html#operators
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.
すみません、わかりにくかったですが、C(5, 2)の時、5//2 = 2 2*4 = 8 8//1 = 8で本来10なのに切り捨てられてずれるのが怖いということでした。
割るのを1からにしたらずれませんね