Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 145 additions & 0 deletions 62. Unique Paths.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
### Step1

- 算数の問題?
- 小数点のずれが怖いので、最初に全部かけてから後で割った
Copy link

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

Copy link
Owner Author

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からにしたらずれませんね


```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)かな?
Copy link

Choose a reason for hiding this comment

The 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)]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

path は 2 個以上ある場合がありますので、複数形にして num_paths とするとよいと思います。

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]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

row が 0 のとき row - 1-1 となり、末尾の要素を参照している点に違和感を感じました。 row が 0 以上の場合にのみ処理をしたほうが良いと思います。 column についても同様です。

Copy link
Owner Author

Choose a reason for hiding this comment

The 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
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

細かい点ですが、 num_path[0] = 1 と代入しておき、 for i in range(1, n): とすると、 if 文が消せると思います。

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]
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

末尾の要素を返せばよいため、 num_path[-1] でも良いと思います。

```

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

おそらく掛け算がビットシフトより遅いのでこういう工夫がされてる?

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

そうです。テクニック周りの話がまとまっている記事があるので、参考になりそうです。この例はfactorial関数についてですが、だいたい同じだと思います。
https://qiita.com/AkariLuminous/items/1b2e964ebabde9419224

Copy link
Owner Author

@nittoco nittoco Jul 16, 2024

Choose a reason for hiding this comment

The 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を返す

こういうのの実装、見ると楽しいことに気づいたが、あんまりハマりすぎても問題が進まないので程々に
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

書くよりも読むほうが大事なので、私はいいと思いますよ。