Skip to content
Open
Show file tree
Hide file tree
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
71 changes: 71 additions & 0 deletions 62-Unique-Paths/note.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
# 62. Unique Paths

https://leetcode.com/problems/unique-paths/

## step1(まず通す)

### 方法1

数学の問題でよくある設定。m - 1 回の下移動と n - 1 回の右移動を並べるときの並べ方の総数。
`((m - 1) + (n - 1))!/((m - 1)! (n - 1)!)`

Python の整数は 64 bit 環境では sys.maxsize が `2 ^ 63 - 1` で、 10 進数で 18 桁ある。ただし、Python ではこれは int の最大値ではなくて、メモリが許せばもっと大きな整数も扱える。`2 ^ 7 - 1` で 1 byte なので、よほどメモリが厳しい環境でないならそこまで気にすることではなさそうか(少なくとも Python を使う用途ではそういう環境は少なそう)

```
# 手元の PC で実験
$ python3
>>> import sys
>>> sys.maxsize
9223372036854775807
>>> large = 10 ** 50
>>> large
100000000000000000000000000000000000000000000000000
>>> type(large)
<class 'int'>
>>> sys.maxsize < large
True
```

```python
from math import factorial


class Solution:
def uniquePaths(self, m: int, n: int) -> int:
return factorial(m + n - 2) // (factorial(m - 1) * factorial(n - 1))
```

`m = <大きい>, n = 2` みたいに偏っていると、答えの大きさに制限があっても↑の素朴なやり方では計算過程で大きすぎる数が発生することがあるが、その場合でも分母の m - 1, n - 1 の大きい方で分子を割るという事前の式変形により `(m + n - 2) * (m + n - 3) * ... * (n - 1) / (n - 1)!` (m - 1が大きい場合)のように桁数をある程度抑えて計算できる。

時間計算量は O(m + n)、空間計算量は O(1) (整数がデカすぎる場合には確証が持てないが、あまり大きくなければこれでいいと思う)

### 方法2(step1.py)

↑のような計算ではない解き方だとすると?

各マス目に対して、そこまでの行き方を左上から順にマス目に書き込んでいけばよい。
ただし、あるマス目への行き方の数は、`そのマスの左のマスまでの行き方 + そのマスの上のマスまでの行き方` である。

このやり方だと、計算過程で出てくる数字が答えより必ず小さくなるので桁数の心配は(そもそも答えが大きすぎるとき以外は)しなくて良さそう。
ただし、m \* n のグリッド全て(効率化しても対角線より上半分)に対してループを回すので時間計算量と空間計算量は O(m \* n) になる。
今回は 1 <= m, n <= 100 なのでかかっても数 ms 〜 数十 ms と予想できる。

## step2(整形&他の人のコードを読む)

変数名:

- way より path の方がいいかも。
- grid よりは num_paths とかの方が良さそう
- https://github.com/Fuminiton/LeetCode/pull/33#discussion_r2061953239
- ただ個人的には、各マス目の path の数を num_paths と呼びたいのでその表には別の名前をつけたいかも。でもいい名前が思いつかないし長すぎても読みづらそうなのでこれを採用
- index_xxx は単に xxx でいいかも

> 内側が掛け算で構わない、つまり、数字が同一オブジェクトであったとしても代入するときに immutable なので置き換えられるので構わないが、List は問題であるという認識をしておきましょう。

https://discord.com/channels/1084280443945353267/1337642831824814192/1363923644635549928

step1 で方法2を書いた際、初回でこのミスをしてしまった。

## step3(10分以内にさっとかける \* 3回)

別オブジェクトを複数回作りたいときは `*` じゃなくて `for` で書く、と決めといた方が間違いづらいかも?
24 changes: 24 additions & 0 deletions 62-Unique-Paths/step1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
from collections import deque


class Solution:
def uniquePaths(self, num_rows: int, num_columns: int) -> int:
grid = [[0] * num_columns for _ in range(num_rows)]
frontier = deque([(0, 0)])

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

row と column の 2 重ループで十分だと思いました。

for row in range(num_rows):
    for column in range(num_columns):
        ...

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

ありがとうございます。

おっしゃる通り単純な 2 重ループでも左→右、上→下の順番が守られるのでそれで十分ですね。気づいていませんでした。

seen = set([(0, 0)])
grid[0][0] = 1
while frontier:
index_row, index_column = frontier.popleft()
num_ways = grid[index_row][index_column]
if index_row + 1 < num_rows:
grid[index_row + 1][index_column] += num_ways
if (index_row + 1, index_column) not in seen:
frontier.append((index_row + 1, index_column))
seen.add((index_row + 1, index_column))
if index_column + 1 < num_columns:
grid[index_row][index_column + 1] += num_ways
if (index_row, index_column + 1) not in seen:
frontier.append((index_row, index_column + 1))
seen.add((index_row, index_column + 1))

return grid[num_rows - 1][num_columns - 1]
23 changes: 23 additions & 0 deletions 62-Unique-Paths/step2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from collections import deque


class Solution:
def uniquePaths(self, num_rows: int, num_columns: int) -> int:
num_paths = [[0] * num_columns for _ in range(num_rows)]
frontier = deque([(0, 0)])
seen = set([(0, 0)])
num_paths[0][0] = 1
while frontier:
row, column = frontier.popleft()
if row + 1 < num_rows:
num_paths[row + 1][column] += num_paths[row][column]
if (row + 1, column) not in seen:
frontier.append((row + 1, column))
seen.add((row + 1, column))
if column + 1 < num_columns:
num_paths[row][column + 1] += num_paths[row][column]
if (row, column + 1) not in seen:
frontier.append((row, column + 1))
seen.add((row, column + 1))

return num_paths[num_rows - 1][num_columns - 1]
23 changes: 23 additions & 0 deletions 62-Unique-Paths/step3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
from collections import deque


class Solution:
def uniquePaths(self, num_rows: int, num_columns: int) -> int:
num_paths = [[0 for _ in range(num_columns)] for _ in range(num_rows)]
frontiers = deque([(0, 0)])
seen = set([(0, 0)])
num_paths[0][0] = 1
while frontiers:
row, column = frontiers.popleft()
if row + 1 < num_rows:
num_paths[row + 1][column] += num_paths[row][column]
if (row + 1, column) not in seen:
frontiers.append((row + 1, column))
seen.add((row + 1, column))
if column + 1 < num_columns:
num_paths[row][column + 1] += num_paths[row][column]
if (row, column + 1) not in seen:
frontiers.append((row, column + 1))
seen.add((row, column + 1))

return num_paths[-1][-1]