Skip to content
80 changes: 80 additions & 0 deletions leetcode/62/memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
# Step 1

典型的なDPの問題

素直に `m * n` の grid を用意して埋めていけばいい。(`step1.py`)
時間計算量: O(mn), 空間計算量: O(mn)

`m * n` 全ての結果を持つ必要はなくて、次のrowを埋めるのに必要なのは直前のrowだけなので、2つのrowだけ持てばいい。(`step1_two_rows.py`)
Copy link

Choose a reason for hiding this comment

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

さらに言えば,rowの更新は左から右の一方向なので1つのrowを持てば十分ですね.つまりcurrent_row[col]を更新する直前,current_row[col]にはprev_row[col]と同じ値が入っているはずなので

current_row[col] += current_row[col - 1]

と更新できます.(可読性は直前と今で別変数に入れた方が良いでしょうが)

時間計算量は O(mn) で変わらずだが、空間計算量は O(m) で済む。m と n を入れ替えても結果は変わらないので、nの方が小さいことがわかっている場合は先にm と n を入れ替えたら良さそう。

> `1 <= m, n <= 100`

なので、最悪でも `10^4` くらいのオーダーの処理しか必要ないので、実行時間は問題ないだろう。
Pythonのintのメモリ使用量は後で調べるとして、Cだったら int が多くの場合 32 bits = 4 bytes で、それが `10^4` 個あると `4 * 10^4 bytes ~= 39KB`か (vectorのオーバーヘッドを無視して)。

具体的な実装はまだ詳しくみれていないが、[Python Maven - Size of integer in Python](https://python.code-maven.com/size-of-integer-in-python) を見ると

```python
import sys

for i in range(0, 200, 10):
num = 2**i
print(sys.getsizeof(num), num)

"""
28 1
28 1024
28 1048576
32 1073741824
32 1099511627776
32 1125899906842624
36 1152921504606846976
36 1180591620717411303424
36 1208925819614629174706176
40 1237940039285380274899124224
40 1267650600228229401496703205376
40 1298074214633706907132624082305024
44 1329227995784915872903807060280344576
44 1361129467683753853853498429727072845824
44 1393796574908163946345982392040522594123776
48 1427247692705959881058285969449495136382746624
48 1461501637330902918203684832716283019655932542976
48 1496577676626844588240573268701473812127674924007424
52 1532495540865888858358347027150309183618739122183602176
52 1569275433846670190958947355801916604025588861116008628224
"""
```

のようなテストが提示されていて、`1` のような小さい数字でも 28 bytes も使用していることがわかった。

# Step 2

## 組み合わせ

[dxxsxsxkxさんのPR](https://github.com/dxxsxsxkx/leetcode/pull/33/changes#diff-e425ab53cee19df2ef6995889ba4e527fc0e29e480a6787c15119f2da066511c)

動的計画法だと初めからわかっていたのでそれに飛びついてしまったが、いつかの数学でやった組み合わせの問題であることも連想できた方が良かったな。
Copy link

Choose a reason for hiding this comment

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

これは僕も同じことしました。
こういうやり方もあるよくらいは言えた方が良さそうですよね。


総移動距離が (m + n - 2) で、そこから m - 1 (か n - 1) を置く場所を選ぶから (m + n - 2)C(m - 1)。
例えばm = 3, n = 4 だったら 2個のdownと3個のright を組み合わせるから5箇所から2個のdown を選んで (例えば DRDRR) 5C2 = 10。

書いてみた -> `step2_combination.py` Pythonだから桁を気にせず掛け合わせてしまっている。

時間計算量: O(min(m, n)), 空間計算量: O(1)

そもそもPythonには [math.comb](https://docs.python.org/3/library/math.html#math.comb) という関数があるらしいのでそれを使えば簡単か。-> `step2_math_comb.py`

## Memoization

そういえば、Memoizationでもいけそうだな -> `step2_memoization.py`

## 1 列 DP

[naoto-iwaseさんのPR](https://github.com/naoto-iwase/leetcode/pull/38/changes#diff-8a6ff63343e74cc80d732f8899ddf515ccf1d52a91f0f73f0d8b022fd23400cbR44)

保持するのが一列だけでも良いのか、いや確かに言われてみれば。-> `step2_one_row_dp.py`

# Step 3

特に工夫もないTabulationが一番わかりやすい。
15 changes: 15 additions & 0 deletions leetcode/62/step1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
path_count = [[0] * n for _ in range(m)]
for i in range(m):
path_count[i][0] = 1
for i in range(n):
path_count[0][i] = 1

for row in range(1, m):
for col in range(1, n):
path_count[row][col] = (
path_count[row - 1][col] + path_count[row][col - 1]
)

return path_count[m - 1][n - 1]
15 changes: 15 additions & 0 deletions leetcode/62/step1_two_rows.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
prev_row = [1] * n
for row in range(1, m):
curr_row = [0] * n
for col in range(n):
if col == 0:
curr_row[col] = 1
continue

curr_row[col] = curr_row[col - 1] + prev_row[col]

prev_row = curr_row

return prev_row[-1]
12 changes: 12 additions & 0 deletions leetcode/62/step2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
count = [[0] * n for _ in range(m)]
for row in range(m):
for col in range(n):
if row == 0 or col == 0:
count[row][col] = 1
continue

count[row][col] = count[row - 1][col] + count[row][col - 1]

return count[m - 1][n - 1]
11 changes: 11 additions & 0 deletions leetcode/62/step2_combination.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
m, n = max(m, n), min(m, n)

numerator = 1
denominator = 1
for i in range(n - 1):
numerator *= m + n - 2 - i
denominator *= n - 1 - i

return numerator // denominator
6 changes: 6 additions & 0 deletions leetcode/62/step2_math_combo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import math


class Solution:
def uniquePaths(self, m: int, n: int) -> int:
return math.comb(m + n - 2, m - 1)
9 changes: 9 additions & 0 deletions leetcode/62/step2_memoization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import functools


class Solution:
@functools.cache
Copy link

Choose a reason for hiding this comment

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

pythonにはこんなのがあるんですね.便利すぎてびっくりしました.

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)
7 changes: 7 additions & 0 deletions leetcode/62/step2_one_row_dp.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
count = [1] * n
for _ in range(1, m):
for col in range(1, n):
count[col] += count[col - 1]
return count[-1]
12 changes: 12 additions & 0 deletions leetcode/62/step3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
class Solution:
def uniquePaths(self, m: int, n: int) -> int:
count = [[0] * n for _ in range(m)]
for row in range(m):
for col in range(n):
if row == 0 or col == 0:
Copy link
Owner Author

Choose a reason for hiding this comment

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

読み直すと、上端か左端かの2つのチェックが一行にまとまっているのがちょっとわかりづらい?
step1.py のように初期化する方が素直かも。

Copy link

Choose a reason for hiding this comment

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

個人的にはこちらの方が読みやすいです.できるだけ同じ枠組みに入れてしまうのが好きと言うのもあるかもしれません.

Copy link
Owner Author

Choose a reason for hiding this comment

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

ありがとうございます、他の方々との感覚のすり合わせができて助かります!

Copy link

Choose a reason for hiding this comment

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

僕は前者の方が目的が明確で読みやすく感じましたが、好みのレベルだと思います。どちらも読みやすいです!

Choose a reason for hiding this comment

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

こういう書き方もあるのかと勉強になりました 🙏

count[row][col] = 1
continue

count[row][col] = count[row - 1][col] + count[row][col - 1]

return count[m - 1][n - 1]