|
| 1 | +""" |
| 2 | +Author : Alexander Pantyukhin |
| 3 | +Date : November 24, 2022 |
| 4 | +
|
| 5 | +Task: |
| 6 | +Given an m x n grid of characters board and a string word, |
| 7 | +return true if word exists in the grid. |
| 8 | +
|
| 9 | +The word can be constructed from letters of sequentially adjacent cells, |
| 10 | +where adjacent cells are horizontally or vertically neighboring. |
| 11 | +The same letter cell may not be used more than once. |
| 12 | +
|
| 13 | +Example: |
| 14 | +
|
| 15 | +Matrix: |
| 16 | +--------- |
| 17 | +|A|B|C|E| |
| 18 | +|S|F|C|S| |
| 19 | +|A|D|E|E| |
| 20 | +--------- |
| 21 | +
|
| 22 | +Word: |
| 23 | +"ABCCED" |
| 24 | +
|
| 25 | +Result: |
| 26 | +True |
| 27 | +
|
| 28 | +Implementation notes: Use backtracking approach. |
| 29 | +At each point, check all neighbors to try to find the next letter of the word. |
| 30 | +
|
| 31 | +leetcode: https://leetcode.com/problems/word-search/ |
| 32 | +
|
| 33 | +""" |
| 34 | + |
| 35 | + |
| 36 | +def word_exists(board: list[list[str]], word: str) -> bool: |
| 37 | + """ |
| 38 | + >>> word_exists([["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], "ABCCED") |
| 39 | + True |
| 40 | + >>> word_exists([["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], "SEE") |
| 41 | + True |
| 42 | + >>> word_exists([["A","B","C","E"],["S","F","C","S"],["A","D","E","E"]], "ABCB") |
| 43 | + False |
| 44 | + >>> word_exists([["A"]], "A") |
| 45 | + True |
| 46 | + >>> word_exists([["A","A","A","A","A","A"], |
| 47 | + ... ["A","A","A","A","A","A"], |
| 48 | + ... ["A","A","A","A","A","A"], |
| 49 | + ... ["A","A","A","A","A","A"], |
| 50 | + ... ["A","A","A","A","A","B"], |
| 51 | + ... ["A","A","A","A","B","A"]], |
| 52 | + ... "AAAAAAAAAAAAABB") |
| 53 | + False |
| 54 | + >>> word_exists([["A"]], 123) |
| 55 | + Traceback (most recent call last): |
| 56 | + ... |
| 57 | + ValueError: The word parameter should be a string of length greater than 0. |
| 58 | + >>> word_exists([["A"]], "") |
| 59 | + Traceback (most recent call last): |
| 60 | + ... |
| 61 | + ValueError: The word parameter should be a string of length greater than 0. |
| 62 | + >>> word_exists([[]], "AB") |
| 63 | + Traceback (most recent call last): |
| 64 | + ... |
| 65 | + ValueError: The board should be a non empty matrix of single chars strings. |
| 66 | + >>> word_exists([], "AB") |
| 67 | + Traceback (most recent call last): |
| 68 | + ... |
| 69 | + ValueError: The board should be a non empty matrix of single chars strings. |
| 70 | + >>> word_exists([["A"], [21]], "AB") |
| 71 | + Traceback (most recent call last): |
| 72 | + ... |
| 73 | + ValueError: The board should be a non empty matrix of single chars strings. |
| 74 | + """ |
| 75 | + |
| 76 | + # Validate board |
| 77 | + board_error_message = ( |
| 78 | + "The board should be a non empty matrix of single chars strings." |
| 79 | + ) |
| 80 | + if not isinstance(board, list) or len(board) == 0: |
| 81 | + raise ValueError(board_error_message) |
| 82 | + |
| 83 | + for row in board: |
| 84 | + if not isinstance(row, list) or len(row) == 0: |
| 85 | + raise ValueError(board_error_message) |
| 86 | + |
| 87 | + for item in row: |
| 88 | + if not isinstance(item, str) or len(item) != 1: |
| 89 | + raise ValueError(board_error_message) |
| 90 | + |
| 91 | + # Validate word |
| 92 | + if not isinstance(word, str) or len(word) == 0: |
| 93 | + raise ValueError( |
| 94 | + "The word parameter should be a string of length greater than 0." |
| 95 | + ) |
| 96 | + |
| 97 | + traverts_directions = [(0, 1), (0, -1), (-1, 0), (1, 0)] |
| 98 | + len_word = len(word) |
| 99 | + len_board = len(board) |
| 100 | + len_board_column = len(board[0]) |
| 101 | + |
| 102 | + # Returns the hash key of matrix indexes. |
| 103 | + def get_point_key(row: int, column: int) -> int: |
| 104 | + """ |
| 105 | + >>> len_board=10 |
| 106 | + >>> len_board_column=20 |
| 107 | + >>> get_point_key(0, 0) |
| 108 | + 200 |
| 109 | + """ |
| 110 | + |
| 111 | + return len_board * len_board_column * row + column |
| 112 | + |
| 113 | + # Return True if it's possible to search the word suffix |
| 114 | + # starting from the word_index. |
| 115 | + def exits_word( |
| 116 | + row: int, column: int, word_index: int, visited_points_set: set[int] |
| 117 | + ) -> bool: |
| 118 | + """ |
| 119 | + >>> board=[["A"]] |
| 120 | + >>> word="B" |
| 121 | + >>> exits_word(0, 0, 0, set()) |
| 122 | + False |
| 123 | + """ |
| 124 | + |
| 125 | + if board[row][column] != word[word_index]: |
| 126 | + return False |
| 127 | + |
| 128 | + if word_index == len_word - 1: |
| 129 | + return True |
| 130 | + |
| 131 | + for direction in traverts_directions: |
| 132 | + next_i = row + direction[0] |
| 133 | + next_j = column + direction[1] |
| 134 | + if not (0 <= next_i < len_board and 0 <= next_j < len_board_column): |
| 135 | + continue |
| 136 | + |
| 137 | + key = get_point_key(next_i, next_j) |
| 138 | + if key in visited_points_set: |
| 139 | + continue |
| 140 | + |
| 141 | + visited_points_set.add(key) |
| 142 | + if exits_word(next_i, next_j, word_index + 1, visited_points_set): |
| 143 | + return True |
| 144 | + |
| 145 | + visited_points_set.remove(key) |
| 146 | + |
| 147 | + return False |
| 148 | + |
| 149 | + for i in range(len_board): |
| 150 | + for j in range(len_board_column): |
| 151 | + if exits_word(i, j, 0, {get_point_key(i, j)}): |
| 152 | + return True |
| 153 | + |
| 154 | + return False |
| 155 | + |
| 156 | + |
| 157 | +if __name__ == "__main__": |
| 158 | + import doctest |
| 159 | + |
| 160 | + doctest.testmod() |
0 commit comments