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
287 changes: 287 additions & 0 deletions medium/63/answer.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,287 @@
# Step1

かかった時間:9min

計算量:
obstacleGrid.length = M
obstacleGrid[i].length = Nとして、

時間計算量:O(M*N)

空間計算量:O(M*N)

DP
```python
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
m = len(obstacleGrid)
n = len(obstacleGrid[0])
num_ways = [[0] * n for _ in range(m)]

if obstacleGrid[0][0] == 1\
Copy link

Choose a reason for hiding this comment

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

PEP8 はこれ嫌がった気がします。
https://peps.python.org/pep-0008/#multiline-if-statements

Copy link
Owner Author

Choose a reason for hiding this comment

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

ありがとうございます。読み落としてました。

The preferred way of wrapping long lines is by using Python’s implied line continuation inside parentheses, brackets and braces. Long lines can be broken over multiple lines by wrapping expressions in parentheses. These should be used in preference to using a backslash for line continuation.

この部分ですね。特にこだわりがあったわけではないので書き方を変更しようと思います。
with-statementsの場合は例外的なんですね。

or obstacleGrid[-1][-1] == 1:
return 0

num_ways[0][0] = 1
Copy link

Choose a reason for hiding this comment

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

この問題設定は、(0, 0) に障害物が来ることないんでしたっけ。

Copy link

Choose a reason for hiding this comment

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

あ、上で弾いていましたね。

for row in range(m):
for col in range(n):
if row == 0 and col == 0:
continue
if obstacleGrid[row][col] == 1:
continue
if row == 0:
num_ways[row][col] = num_ways[row][col - 1]
continue
if col == 0:
num_ways[row][col] = num_ways[row - 1][col]
continue
num_ways[row][col] = (
num_ways[row][col - 1] + num_ways[row - 1][col]
)

return num_ways[-1][-1]
```
思考ログ:
- 前回のUniquePathsIのやり方を継承しつつ、obstacleGridを参照しながら行けない場所を考慮できるようにする
- ばーっと書いてしまったが、for文の中の処理を切り出して見通しをよくしたい(初手これを書いたので一応載せている)

上を少しまとめた
```python
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
def _count_unique_paths(row: int, col: int) -> int:
# gridのセル(row, col)までのパス数を数える
if row == 0:
return num_ways[row][col - 1]
if col == 0:
return num_ways[row - 1][col]

return num_ways[row][col - 1] + num_ways[row - 1][col]

m = len(obstacleGrid)
n = len(obstacleGrid[0])
num_ways = [[0] * n for _ in range(m)]

if obstacleGrid[0][0] == 1\
or obstacleGrid[-1][-1] == 1:
return 0

num_ways[0][0] = 1
for row in range(m):
for col in range(n):
if row == 0 and col == 0:
continue
if obstacleGrid[row][col] == 1:
continue
num_ways[row][col] = _count_unique_paths(row, col)

return num_ways[-1][-1]
```
思考ログ:

再帰
```python
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
@cache
Copy link

Choose a reason for hiding this comment

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

inner-function の場合は、毎回、別のオブジェクトとして生成されるので、この関数を2回呼んでも cache が再利用されないことを試して確認しておいてください。id 使うといいです。

Copy link

Choose a reason for hiding this comment

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

つまり、uniquePathsWithObstacles を2回呼んでも問題がないということです。

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
Owner Author

Choose a reason for hiding this comment

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

def func():
    def _inner_func():
        pass
    return _inner_func

if __name__ == '__main__':
    f = func()
    print(id(f)) # 4298894208
    f = func()
    print(id(f)) # 4298894352

こんなイメージでしょうか。

Copy link

Choose a reason for hiding this comment

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

あ、そうです。そういうわけで、違うオブジェクトが作られます。

def _count_unique_paths(row: int, col: int) -> int:
assert row >= 0 or col >= 0, 'row and col must be positive integer values.'

if obstacleGrid[row][col] == 1:
return 0
if row == 0 and col == 0:
return 1
if row == 0:
return _count_unique_paths(row, col - 1)
if col == 0:
return _count_unique_paths(row - 1, col)

return _count_unique_paths(row - 1, col)\
+ _count_unique_paths(row, col - 1)

return _count_unique_paths(
len(obstacleGrid) - 1,
len(obstacleGrid[0]) - 1
```
思考ログ:

1-DP
```python
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
# 11:59
def _check_if_valid_cell(row: int, col: int, is_transposed: bool) -> bool:
Copy link

Choose a reason for hiding this comment

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

validは、何がvalidなのか読み取りづらかったです
通れる、のようなニュアンスがあるとわかりやすい気がします

Copy link

Choose a reason for hiding this comment

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

と思いましたが、これ通れなくてもtrueになることありますね(難しい)

Copy link
Owner Author

Choose a reason for hiding this comment

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

そうですね。見直して手抜きだなと自分でも思いました。
下でご提案いただいているis_obstacleで良いかもしれませんね。

if is_transposed:
return obstacleGrid[col][row] == 0
else:
return obstacleGrid[row][col] == 0

if obstacleGrid[0][0] == 1\
or obstacleGrid[-1][-1] == 1:
return 0

m = len(obstacleGrid)
n = len(obstacleGrid[0])
is_transposed = False
if m < n:
n, m = m, n
is_transposed = True

num_ways = [0] * n
num_ways[0] = 1
for row in range(m):
for col in range(n):
if _check_if_valid_cell(row, col, is_transposed):
if col == 0:
Copy link

Choose a reason for hiding this comment

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

趣味の範囲ですが、この方がちょっとすっきりしますかねえ

num_paths[0] = 1
for row in range(height):
  for column in range(width):
      if is_obstacle(row, column, is_transposed):
          num_paths[column] = 0
          continue
      if column == 0:
          continue
      num_paths[column] += num_paths[column - 1]

continue
num_ways[col] += num_ways[col - 1]
else:
num_ways[col] = 0

return num_ways[-1]
```
思考ログ:
- メモリ節約ver
- 今回は、n, mを入れ替えた時に参照するobstacleGridにも影響が及ぶ
- これも転置しちゃえば良いのだが、破壊的な変更をしたくないし、転置した情報を別に用意したらこれはこれで本末転倒なことに
- 転置フラグで管理するようにしたが、どうだろうか
- ```num_ways```の0番目の要素の扱いで少しハマった
- ```num_ways```がどんな要素を持つ配列なのか、それは意図したものなのか、確認がまだ甘い
- colのループの前で```col = 0```だけ処理してしまってもいいが、うーん

# Step2

講師役目線でのセルフツッコミポイント:
- マジックナンバー(obstacle)

参考にした過去ログなど:
- https://github.com/Ryotaro25/leetcode_first60/pull/40
- https://github.com/seal-azarashi/leetcode/pull/32
- https://github.com/Yoshiki-Iwasa/Arai60/pull/49
- BFS
- https://github.com/nittoco/leetcode/pull/27
- https://github.com/fhiyo/leetcode/pull/35
- https://github.com/goto-untrapped/Arai60/pull/33
- 以下の条件のまとめ方が頭になかった
- https://github.com/goto-untrapped/Arai60/pull/33/files#r1665011732
- https://github.com/YukiMichishita/LeetCode/pull/15
- https://github.com/sakupan102/arai60-practice/pull/35
- シンプルな1DP
- https://github.com/SuperHotDogCat/coding-interview/pull/17
- https://github.com/shining-ai/leetcode/pull/34
- https://github.com/hayashi-ay/leetcode/pull/44
- 値を受け取る、渡す

DP
```python
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
OBSTACLE = 1
if obstacleGrid[0][0] == OBSTACLE \
or obstacleGrid[-1][-1] == OBSTACLE:
return 0

m = len(obstacleGrid)
n = len(obstacleGrid[0])
num_ways = [[0] * n for _ in range(m)]
num_ways[0][0] = 1
for row in range(m):
for col in range(n):
if row == 0 and col == 0:
continue
if obstacleGrid[row][col] == OBSTACLE:
continue
if row > 0:
num_ways[row][col] += num_ways[row - 1][col]
if col > 0:
num_ways[row][col] += num_ways[row][col - 1]

return num_ways[-1][-1]
```
思考ログ:
- 少し処理の順序を変えた
- マジックナンバーの解消
- DP受け取りの分岐を変更

DP、事前に初期化
```python
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
OBSTACLE = 1
if obstacleGrid[0][0] == OBSTACLE \
or obstacleGrid[-1][-1] == OBSTACLE:
return 0

m = len(obstacleGrid)
n = len(obstacleGrid[0])
def initialize_num_ways() -> list[list[int]]:
num_ways = [[0] * n for _ in range(m)]
for row in range(m):
if obstacleGrid[row][0] == OBSTACLE:
break
num_ways[row][0] = 1
for col in range(n):
if obstacleGrid[0][col] == OBSTACLE:
break
num_ways[0][col] = 1

return num_ways

num_ways = initialize_num_ways()
for row in range(1, m):
for col in range(1, n):
if obstacleGrid[row][col] == OBSTACLE:
continue
num_ways[row][col] = num_ways[row - 1][col] + num_ways[row][col - 1]

return num_ways[-1][-1]
```
思考ログ:
- 後半がシンプルに書けて良い

# Step3

かかった時間:6min

```python
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
OBSTACLE = 1
if obstacleGrid[0][0] == OBSTACLE \
or obstacleGrid[-1][-1] == OBSTACLE:
return 0

m = len(obstacleGrid)

Choose a reason for hiding this comment

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

m, nという命名よりかはnum_rowsnum_colsとかの方が読みやすいかなと思いました

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とm、colとnどちらかに統一した方が見やすいなと思いました。

n = len(obstacleGrid[0])
def initialize_num_ways():
num_ways = [[0] * n for _ in range(m)]
for row in range(m):
if obstacleGrid[row][0] == OBSTACLE:
break
num_ways[row][0] = 1
for col in range(n):
if obstacleGrid[0][col] == OBSTACLE:
break
num_ways[0][col] = 1

return num_ways

num_ways = initialize_num_ways()
for row in range(1, m):
for col in range(1, n):
if obstacleGrid[row][col] == OBSTACLE:
continue
num_ways[row][col] = num_ways[row - 1][col] + num_ways[row][col - 1]

return num_ways[-1][-1]
```
思考ログ:
- 個人的には後半の処理がすっきりするのが良かった
- 1DPは```min(n, m)```の最適化までやりたくなるが、コードが込み入ってくるので2DPでも良い気がする

# Step4

```python
```
思考ログ: