|
| 1 | +from typing import List |
| 2 | +from unittest import TestCase, main |
| 3 | + |
| 4 | + |
| 5 | +class Node: |
| 6 | + |
| 7 | + def __init__(self, key, data=None): |
| 8 | + self.key = key |
| 9 | + self.data = data |
| 10 | + self.children = {} |
| 11 | + |
| 12 | + |
| 13 | +class Trie: |
| 14 | + |
| 15 | + def __init__(self): |
| 16 | + self.root = Node(None) |
| 17 | + |
| 18 | + def insert(self, word: str) -> None: |
| 19 | + curr_node = self.root |
| 20 | + for char in word: |
| 21 | + if char not in curr_node.children: |
| 22 | + curr_node.children[char] = Node(char) |
| 23 | + |
| 24 | + curr_node = curr_node.children[char] |
| 25 | + |
| 26 | + curr_node.data = word |
| 27 | + |
| 28 | + |
| 29 | +class Solution: |
| 30 | + def findWords(self, board: List[List[str]], words: List[str]) -> List[str]: |
| 31 | + return self.solve_trie_dfs(board, words) |
| 32 | + |
| 33 | + """ |
| 34 | + * Constraints: |
| 35 | + 1. m == board.length |
| 36 | + 2. n == board[i].length |
| 37 | + 3. 1 <= m, n <= 12 |
| 38 | + 4. board[i][j] is a lowercase English letter. |
| 39 | + 5. 1 <= words.length <= 3 * 104 |
| 40 | + 6. 1 <= words[i].length <= 10 |
| 41 | + 7. words[i] consists of lowercase English letters. |
| 42 | + 8. All the strings of words are unique. |
| 43 | +
|
| 44 | + Runtime: 6439 ms (Beats 26.38%) |
| 45 | + Time Complexity: O(R * C * (4 ^ max L)) |
| 46 | + - word의 최대 길이를 max L, words의 길이를 K라 하면, trie에 words를 모두 insert하는데 O(max L * K), upper bound |
| 47 | + - board의 각 grid에서 조회하는데 O(R * C) |
| 48 | + - grid마다 dfs 호출하는데, dfs의 방향은 4곳이고, 호출 스택의 최대 깊이는 max L 이므로, * O(4 ^ max L) |
| 49 | + > O(max L * K) + O(R * C) * O(4 ^ max L) ~= O(R * C * (4 ^ max L)) |
| 50 | +
|
| 51 | + Memory: 19.04 MB (Beats 20.79%) |
| 52 | + Space Complexity: O(max L * K) |
| 53 | + - trie의 공간 복잡도는 O(max L * K), upper bound |
| 54 | + - board의 각 grid에서 dfs를 호출하고, dfs 호출 스택의 최대 깊이는 max L 이므로 O(max L) |
| 55 | + - result의 최대 크기는 words의 길이와 같으므로 O(K), upper bound |
| 56 | + > O(max L * K) + O(max L) + O(K) ~= O(max L * K) |
| 57 | + """ |
| 58 | + def solve_trie_dfs(self, board: List[List[str]], words: List[str]) -> List[str]: |
| 59 | + MAX_R, MAX_C = len(board), len(board[0]) |
| 60 | + DIRS = [(0, 1), (1, 0), (0, -1), (-1, 0)] |
| 61 | + |
| 62 | + trie = Trie() |
| 63 | + for word in words: |
| 64 | + trie.insert(word) |
| 65 | + |
| 66 | + def dfs(curr: Node, r: int, c: int, path: str): |
| 67 | + nonlocal result |
| 68 | + |
| 69 | + if not (0 <= r < MAX_R and 0 <= c < MAX_C): |
| 70 | + return |
| 71 | + |
| 72 | + if board[r][c] == "#": |
| 73 | + return |
| 74 | + |
| 75 | + char = board[r][c] |
| 76 | + if char not in curr.children: |
| 77 | + return |
| 78 | + |
| 79 | + post = curr.children[char] |
| 80 | + if post.data: |
| 81 | + result.add(post.data) |
| 82 | + |
| 83 | + board[r][c] = "#" |
| 84 | + for dir_r, dir_c in DIRS: |
| 85 | + dfs(post, r + dir_r, c + dir_c, path + char) |
| 86 | + board[r][c] = char |
| 87 | + |
| 88 | + result = set() |
| 89 | + for r in range(MAX_R): |
| 90 | + for c in range(MAX_C): |
| 91 | + if board[r][c] in trie.root.children: |
| 92 | + dfs(trie.root, r, c, "") |
| 93 | + |
| 94 | + return list(result) |
| 95 | + |
| 96 | + |
| 97 | +class _LeetCodeTestCases(TestCase): |
| 98 | + def test_1(self): |
| 99 | + board = [["o", "a", "a", "n"], ["e", "t", "a", "e"], ["i", "h", "k", "r"], ["i", "f", "l", "v"]] |
| 100 | + words = ["oath","pea","eat","rain"] |
| 101 | + output = ["eat","oath"] |
| 102 | + self.assertEqual(Solution.findWords(Solution(), board, words), output) |
| 103 | + |
| 104 | + def test_2(self): |
| 105 | + board = [["a","b"],["c","d"]] |
| 106 | + words = ["abcb"] |
| 107 | + output = [] |
| 108 | + self.assertEqual(Solution.findWords(Solution(), board, words), output) |
| 109 | + |
| 110 | + |
| 111 | +if __name__ == '__main__': |
| 112 | + main() |
0 commit comments