diff --git a/0079.Word-Search/memo.md b/0079.Word-Search/memo.md new file mode 100644 index 0000000..f059eac --- /dev/null +++ b/0079.Word-Search/memo.md @@ -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 を持たせておくのか。これは人のコードを見ないと書けなかったかもしれない。 + +再帰をスタックに置き換える場合、フレーウの状態を明示的に持つ必要がある。 diff --git a/0079.Word-Search/step1.py b/0079.Word-Search/step1.py new file mode 100644 index 0000000..4d9193c --- /dev/null +++ b/0079.Word-Search/step1.py @@ -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 + 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 diff --git a/0079.Word-Search/step1_revised.py b/0079.Word-Search/step1_revised.py new file mode 100644 index 0000000..9403c80 --- /dev/null +++ b/0079.Word-Search/step1_revised.py @@ -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 diff --git a/0079.Word-Search/step2_loop.py b/0079.Word-Search/step2_loop.py new file mode 100644 index 0000000..f75b4ac --- /dev/null +++ b/0079.Word-Search/step2_loop.py @@ -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 diff --git a/0079.Word-Search/step2_optimization.py b/0079.Word-Search/step2_optimization.py new file mode 100644 index 0000000..12b881e --- /dev/null +++ b/0079.Word-Search/step2_optimization.py @@ -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 diff --git a/0079.Word-Search/step3.py b/0079.Word-Search/step3.py new file mode 100644 index 0000000..6b73335 --- /dev/null +++ b/0079.Word-Search/step3.py @@ -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