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
45 changes: 45 additions & 0 deletions 0079.Word-Search/memo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# 79. Word Search

## step1

全ての位置から探索する解法が思いつく。最悪だと
(m * n * 3^L) / 10^7 <= (36*3^15)/ 10^7 = 50
かかるが大丈夫だろうか。とりあえず実装してみる。

15mぐらいでかけてテストもクリアした。間違えた点:初期開始点のseenをTrueにし忘れた。

> Follow up: Could you use search pruning to make your solution faster with a larger board?

これを考えたいが案は思いつかず答えを見る。

## step2

### 他の人のコード

https://github.com/huyfififi/coding-challenges/pull/61

> というものが提示されていた。これを入れるだけでLeetCode上の実行時間ランキングでの順位が大きく上がった。

> また、wordをひっくり返して word search を行っても同じ結果が得られるので、wordの先頭と末尾の文字のboard上での数を比べて、末尾の文字の個数の方が小さかったら word をひっくり返して search した方がDFSを始める回数が少なく済む。

この通りにやったら確かにBeat 100msとなった

片方しか行わないと遅くなるのでどちらも効果がある。

> なるほど、確かに言われてみれば納得感はあるが、面接中にスラスラと思いつけるかは自信がない。word をひっくり返して探索しても同じ、というのは問題文を読んでいて気づけた方がいいだろうな。

自分はどちらも思いつかないのでまだまだなのだと思う。


https://github.com/potrue/leetcode/pull/74

早期リターン


https://github.com/thonda28/leetcode/pull/14

再帰 -> ループを自分も書いておく

次に試す方向の index を持たせておくのか。これは人のコードを見ないと書けなかったかもしれない。

再帰をスタックに置き換える場合、フレーウの状態を明示的に持つ必要がある。
41 changes: 41 additions & 0 deletions 0079.Word-Search/step1.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import itertools


class Solution:
def exist(self, board: list[list[str]], word: str) -> bool:
if not board or not board[0]:
raise ValueError("invalid board")

num_row = len(board)
num_col = len(board[0])

def traverse(row: int, col: int, i: int, seen: list[list][bool]) -> bool:
if not word[i] == board[row][col]:
return False
if i == len(word) - 1:
return True
for row_next, col_next in (
(row + 1, col),
(row - 1, col),
(row, col + 1),
(row, col - 1),
):
if (
0 <= row_next < num_row
and 0 <= col_next < num_col
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

and は行末に書くこともあるようです。

参考までにスタイルガイドへのリンクを共有いたします。

https://google.github.io/styleguide/pyguide.html#32-line-length

Yes: if (width == 0 and height == 0 and
color == 'red' and emphasis == 'strong'):

なお、このスタイルガイドは“唯一の正解”というわけではなく、数あるガイドラインの一つに過ぎません。チームによって重視される書き方や慣習も異なります。そのため、ご自身の中に基準を持ちつつも、最終的にはチームの一般的な書き方に合わせることをお勧めします。

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.

リンク先の記事によると、PEP 8では演算子の前に改行を行うことが推奨されているようです。ただし、2016年以前は演算子の後が推奨されていたようです。
https://rcmdnk.com/blog/2019/11/04/computer-python/
自分はこれに倣って以降演算子の前に改行を行うように書こうと思います。

自分は改行をフォーマッタに任せて意識していなかったので勉強になりました。

and not seen[row_next][col_next]
):
seen[row_next][col_next] = True
if traverse(row_next, col_next, i + 1, seen):
return True
seen[row_next][col_next] = False

return False

for row, col in itertools.product(range(num_row), range(num_col)):
seen = [[False] * num_col for _ in range(num_row)]
seen[row][col] = True
if traverse(row, col, 0, seen):
return True

return False
43 changes: 43 additions & 0 deletions 0079.Word-Search/step1_revised.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import itertools


class Solution:
def exist(self, board: list[list[str]], word: str) -> bool:
if not board or not board[0]:
raise ValueError("invalid board")

num_row = len(board)
num_col = len(board[0])

def traverse(row: int, col: int, i: int, seen: list[list][bool]) -> bool:
if not word[i] == board[row][col]:
return False
if i == len(word) - 1:
return True
for row_next, col_next in (
(row + 1, col),
(row - 1, col),
(row, col + 1),
(row, col - 1),
):
# fmt: off
if (
0 <= row_next < num_row and
0 <= col_next < num_col and
not seen[row_next][col_next]
):
# fmt: on
seen[row_next][col_next] = True
if traverse(row_next, col_next, i + 1, seen):
return True
seen[row_next][col_next] = False

return False

for row, col in itertools.product(range(num_row), range(num_col)):
seen = [[False] * num_col for _ in range(num_row)]
seen[row][col] = True
if traverse(row, col, 0, seen):
return True

return False
68 changes: 68 additions & 0 deletions 0079.Word-Search/step2_loop.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import itertools
import collections


class Solution:
def exist(self, board: list[list[str]], word: str) -> bool:
if not board or not board[0]:
raise ValueError("invalid board")

num_row = len(board)
num_col = len(board[0])

board_char_to_count = collections.Counter(itertools.chain.from_iterable(board))
word_char_to_count = collections.Counter(word)

for ch in word_char_to_count:
if board_char_to_count[ch] < word_char_to_count[ch]:
return False

if board_char_to_count[word[-1]] < board_char_to_count[word[0]]:
word = word[::-1]

directions = (
(1, 0),
(-1, 0),
(0, 1),
(0, -1),
)

def start_from(row_start: int, col_start: int) -> bool:
if board[row_start][col_start] != word[0]:
return False

seen = [[False] * num_col for _ in range(num_row)]
seen[row_start][col_start] = True
stack = [(row_start, col_start, 0, 0)]

while stack:
row, col, letter_index, direction_index = stack[-1]
if letter_index == len(word) - 1:
return True

if direction_index == len(directions):
seen[row][col] = False
stack.pop()
continue

d_row, d_col = directions[direction_index]
stack[-1] = (row, col, letter_index, direction_index + 1)
row_next = row + d_row
col_next = col + d_col
next_letter_index = letter_index + 1
if (
0 <= row_next < num_row
and 0 <= col_next < num_col
and not seen[row_next][col_next]
and board[row_next][col_next] == word[next_letter_index]
):
seen[row_next][col_next] = True
stack.append((row_next, col_next, next_letter_index, 0))

return False

for row, col in itertools.product(range(num_row), range(num_col)):
if start_from(row, col):
return True

return False
54 changes: 54 additions & 0 deletions 0079.Word-Search/step2_optimization.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import itertools
import collections


class Solution:
def exist(self, board: list[list[str]], word: str) -> bool:
if not board or not board[0]:
raise ValueError("invalid board")

num_row = len(board)
num_col = len(board[0])

board_char_to_count = collections.Counter(itertools.chain.from_iterable(board))
word_char_to_count = collections.Counter(word)

for ch in word_char_to_count:
if board_char_to_count[ch] < word_char_to_count[ch]:
return False

if board_char_to_count[word[-1]] < board_char_to_count[word[0]]:
word = word[::-1]

def traverse(
row: int, col: int, letter_index: int, seen: list[list][bool]
) -> bool:
if not word[letter_index] == board[row][col]:
return False
if letter_index == len(word) - 1:
return True
for row_next, col_next in (
(row + 1, col),
(row - 1, col),
(row, col + 1),
(row, col - 1),
):
if (
0 <= row_next < num_row
and 0 <= col_next < num_col
and not seen[row_next][col_next]
):
seen[row_next][col_next] = True
if traverse(row_next, col_next, letter_index + 1, seen):
return True
seen[row_next][col_next] = False

return False

for row, col in itertools.product(range(num_row), range(num_col)):
seen = [[False] * num_col for _ in range(num_row)]
seen[row][col] = True
if traverse(row, col, 0, seen):
return True

return False
65 changes: 65 additions & 0 deletions 0079.Word-Search/step3.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import itertools
import collections


class Solution:
def exist(self, board: list[list[str]], word: str) -> bool:
if not board or not board[0]:
raise ValueError("invalid board")

num_row = len(board)
num_col = len(board[0])

board_char_to_count = collections.Counter(itertools.chain.from_iterable(board))
word_char_to_count = collections.Counter(word)

for ch in word_char_to_count:
if board_char_to_count[ch] < word_char_to_count[ch]:
return False

if board_char_to_count[word[-1]] < board_char_to_count[word[0]]:
word = word[::-1]

directions = (
(1, 0),
(-1, 0),
(0, 1),
(0, -1),
)

def start_from(row_start: int, col_start: int) -> bool:
if board[row_start][col_start] != word[0]:
return False

seen = [[False] * num_col for _ in range(num_row)]
seen[row_start][col_start] = True
stack = [(row_start, col_start, 0, 0)]

while stack:
row, col, letter_index, direction_index = stack[-1]
if letter_index == len(word) - 1:
return True

if direction_index == len(directions):
seen[row][col] = False
stack.pop()
continue

stack[-1] = (row, col, letter_index, direction_index + 1)
dr, dc = directions[direction_index]
row_next = row + dr
col_next = col + dc
if (
0 <= row_next < num_row
and 0 <= col_next < num_col
and board[row_next][col_next] == word[letter_index + 1]
and not seen[row_next][col_next]
):
seen[row_next][col_next] = True
stack.append((row_next, col_next, letter_index + 1, 0))

for row, col in itertools.product(range(num_row), range(num_col)):
if start_from(row, col):
return True

return False