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
24 changes: 24 additions & 0 deletions 63/memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
# 63. Unique Paths II

- sol1.py: dpでかいた
- sol2.py: 初期化方法と変数名を改善

https://github.com/olsen-blue/Arai60/pull/34#pullrequestreview-2636297197
> コンパイラ言語ではif文(機械語にしたときの分岐命令)って(分岐予測に失敗するとパイプラインの工程を最初からやり直さないといけないので)他の命令より時間がかかるんです
> なので、可読性の面でも速度の面でもこのfor文は分けた方がよりよいですね
>でも、Pythonはインタプリタ言語(そもそもプログラムの解釈・実行に大量に分岐命令が使われてると思う)なので速度の観点では気にするような速度差は生まれないかもしれません

これは勉強になった。
つまり、CやRustなどんコンパイラ言語では、分岐予測に失敗するとオーバーヘッドが発生するので、for文内のif文はなるべく分けた方が良い。
つまり、事前に決まっている分岐(0行目or0列目など)はfor文の外で行った方が良い。
しかし、Pythonの場合はこれを気にする必要はなさそう。


https://github.com/Fuminiton/LeetCode/pull/34#discussion_r2052772608
> [0][0]にアクセスする前に一応チェックしてもいいかもしれませんね。問題文に制約があるにせよ。

これはたまたまできていた

https://algo-method.com/descriptions/78
配るDPともらうDP
配るDPで書いた:sol3.py
29 changes: 29 additions & 0 deletions 63/sol1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
if not obstacleGrid or not obstacleGrid[0]:
return 0

num_rows, num_cols = len(obstacleGrid), len(obstacleGrid[0])

EMPTY = 0

if obstacleGrid[0][0] != EMPTY:
return 0

unique_paths_per_row = [1] + [0] * (num_cols - 1)

col = 1
while col < num_cols and obstacleGrid[0][col] == EMPTY:
unique_paths_per_row[col] = 1
col += 1
Comment on lines +15 to +18
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

好みの問題ですがcolがこのwhile内でしか使われていないのでfor文にしても良いと思いました.

        for col in range(1, num_cols):
            if obstacleGrid[0][col] != EMPTY:
                break
            unique_paths_per_row[col] = 1

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.

なるほど、その書き方もありますね。
ただ今回は「while の条件式の中にループが続く条件を書きたい」という意図で現状のものを採用しようと思います。

Comment on lines +15 to +18
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に関するforループの範囲を以下のようにすれば動くのではないでしょうか.

for row in range(0, num_rows):

あるマスの値が,その左のマスの値と上のマスの値の和で決まるという仕組みからすると,コメント先の部分の処理を後ろのforループに含んでしまうのも不自然ではないと感じます.

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.

ああ、その通りですね。
sol2.pyでその点を改善した初期化にしたようです。


for row in range(1, num_rows):
for col in range(0, num_cols):
if obstacleGrid[row][col] != EMPTY:
unique_paths_per_row[col] = 0
continue
if col == 0:
continue
unique_paths_per_row[col] += unique_paths_per_row[col - 1]

return unique_paths_per_row[-1]
19 changes: 19 additions & 0 deletions 63/sol2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
if not obstacleGrid or not obstacleGrid[0]:
return 0

n_row, n_col = len(obstacleGrid), len(obstacleGrid[0])

unique_paths_per_row = [0] * n_col
unique_paths_per_row[0] = 0 if obstacleGrid[0][0] else 1
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

obstacleGrid[0][0] == 0の時はsol1.pyのようにearly returnする方が無駄なforループを回さないで済むので好きです.

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.

そうですね。分かりやすさの意味でもその方が良いと思うので採用させていただきます。
(書いた時にはまとめて処理できるが良いと考えたんだと思います)


for row in range(n_row):
for col in range(n_col):
if obstacleGrid[row][col]:
unique_paths_per_row[col] = 0
continue
elif col > 0:
unique_paths_per_row[col] += unique_paths_per_row[col - 1]
Comment on lines +13 to +17
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

正誤には関係ありませんが,if ... elif ... の形でその後にコードが登場しない場合,if分岐内でのconitnueは不要ですね.
(今後コードを書き加える場合,continueすべきケースとすべきでないケースの両方があり得そうなので特段continueを入れる必要性は感じられませんでした.)

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.

そのとおりですね。If...continueとするならelifではなくifの方が適切ですね。
今回はcontinueを消そうと思います。


return unique_paths_per_row[-1]
20 changes: 20 additions & 0 deletions 63/sol2_revised.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
if not obstacleGrid or not obstacleGrid[0]:
return 0

n_row, n_col = len(obstacleGrid), len(obstacleGrid[0])

unique_paths_per_row = [0] * n_col
if obstacleGrid[0][0] == 1:
return 0
unique_paths_per_row[0] = 1

for row in range(n_row):
for col in range(n_col):
if obstacleGrid[row][col]:
unique_paths_per_row[col] = 0
elif col > 0:
unique_paths_per_row[col] += unique_paths_per_row[col - 1]

return unique_paths_per_row[-1]
30 changes: 30 additions & 0 deletions 63/sol3.py
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

メモリ使用量が増えるのをやむなしで、2次元配列(フルのテーブル)で書いたほうが、配るDPは分かりやすいと思いました。また、最終行は配らなくていいとか、障害物があったら0に潰して配れない(こっちは最終行も処理する必要がある)、など制御が比較的面倒で、配るよりも貰う方が書きやすそうに感じました。

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.

「配るよりも貰う方が書きやすそう」は同意します。
配るDPでも直前の二行を保持しておけば良いので、一次元にする必要はないのではないかとと個人的には思います。

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

配るDPでも直前の二行を保持しておけば良いので、一次元にする必要はない

そのとおりで、処理的には問題ないのですが、書きやすさ(バグの埋め込みやすさ)・読みやすさの観点からは、2次元配列の方が優れていそうだな、と感じました。unique_paths_per_rowunique_paths_next_row の入れ替え(更新)の必要がなかったり、最終行は入れ替えしないという条件が理解しやすかったりしそうです。

Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
class Solution:
def uniquePathsWithObstacles(self, obstacleGrid: List[List[int]]) -> int:
if not obstacleGrid or not obstacleGrid[0]:
return 0

n_row, n_col = len(obstacleGrid), len(obstacleGrid[0])

unique_paths_per_row = [0] * n_col
unique_paths_per_row[0] = 0 if obstacleGrid[0][0] else 1

for row in range(n_row):
unique_paths_next_row = [0] * n_col
for col in range(n_col):
if obstacleGrid[row][col]:
unique_paths_per_row[col] = 0
continue

paths = unique_paths_per_row[col]
if paths == 0:
continue

if col + 1 < n_col:
unique_paths_per_row[col + 1] += paths
if row + 1 < n_row:
unique_paths_next_row[col] += paths

if row + 1 < n_row:
unique_paths_per_row = unique_paths_next_row
Comment on lines +11 to +28
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

これは単に好みの問題ですが,sol2.pyで行ったような更新の方が分岐が少なくわかりやすく感じました.

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.

同意します。自分が素直に思いついたのもsol2.pyの方でした。
sol3.pyは配るDPの練習として書きました。


return unique_paths_per_row[-1]