|
| 1 | +class TrieNode: |
| 2 | + def __init__(self): |
| 3 | + self.children = {} |
| 4 | + self.is_end_word = False |
| 5 | + |
| 6 | + |
| 7 | +class WordDictionary: |
| 8 | + |
| 9 | + def __init__(self): |
| 10 | + self.root = TrieNode() |
| 11 | + |
| 12 | + def addWord(self, word: str) -> None: |
| 13 | + node = self.root |
| 14 | + for char in word: |
| 15 | + if char not in node.children: |
| 16 | + node.children[char] = TrieNode() |
| 17 | + node = node.children[char] |
| 18 | + node.is_end_word = True |
| 19 | + |
| 20 | + def search(self, word: str) -> bool: |
| 21 | + return self._dfs(self.root, word, 0) |
| 22 | + |
| 23 | + def _dfs(self, node: TrieNode, word: str, index: int) -> bool: |
| 24 | + # 단어 끝에 도달하면 is_end_word 확인 |
| 25 | + if index == len(word): |
| 26 | + return node.is_end_word |
| 27 | + |
| 28 | + char = word[index] |
| 29 | + |
| 30 | + if char == '.': |
| 31 | + # '.'는 모든 자식 노드 탐색 |
| 32 | + for child in node.children.values(): |
| 33 | + if self._dfs(child, word, index + 1): |
| 34 | + return True |
| 35 | + return False |
| 36 | + else: |
| 37 | + # 일반 문자는 해당 자식으로 이동 |
| 38 | + if char not in node.children: |
| 39 | + return False |
| 40 | + return self._dfs(node.children[char], word, index + 1) |
| 41 | + |
| 42 | + |
| 43 | +""" |
| 44 | +================================================================================ |
| 45 | +풀이 과정 |
| 46 | +================================================================================ |
| 47 | +
|
| 48 | +[1차 시도] 길이별 그룹화 + 브루트포스 매칭 |
| 49 | +──────────────────────────────────────────────────────────────────────────────── |
| 50 | +1. 아이디어: 같은 길이 단어끼리 묶고, 하나씩 패턴 비교 |
| 51 | + words = { 3: ["bad", "dad", "mad"] } |
| 52 | + search(".ad") → 모든 길이 3 단어와 비교 |
| 53 | +
|
| 54 | +2. 문제점: Time Limit Exceeded! |
| 55 | + - 같은 길이 단어가 많으면 O(N × L) 반복 |
| 56 | + - LeetCode 테스트케이스에서 시간 초과 |
| 57 | +
|
| 58 | +3. 더 효율적인 방법 필요 → Trie로 접근 |
| 59 | +
|
| 60 | +──────────────────────────────────────────────────────────────────────────────── |
| 61 | +[2차 시도] Trie (트라이) 자료구조 |
| 62 | +──────────────────────────────────────────────────────────────────────────────── |
| 63 | +4. Trie 구조 (bad, dad, mad 저장 후): |
| 64 | +
|
| 65 | + root |
| 66 | + ├── 'b' → 'a' → 'd' (is_end_word=True) |
| 67 | + ├── 'd' → 'a' → 'd' (is_end_word=True) |
| 68 | + └── 'm' → 'a' → 'd' (is_end_word=True) |
| 69 | +
|
| 70 | +5. 동작 예시: |
| 71 | +
|
| 72 | + search("bad"): |
| 73 | + root → 'b' → 'a' → 'd' → is_end_word=True → True |
| 74 | +
|
| 75 | + search(".ad"): |
| 76 | + root → '.' (모든 자식 탐색) |
| 77 | + → 'b' → 'a' → 'd' → True (첫 번째에서 찾음!) |
| 78 | +
|
| 79 | + search("b.."): |
| 80 | + root → 'b' → '.' (모든 자식) |
| 81 | + → 'a' → '.' (모든 자식) |
| 82 | + → 'd' → True |
| 83 | +
|
| 84 | +6. 왜 Trie가 더 빠른가? |
| 85 | + - 정확한 문자: O(1)로 해당 자식만 탐색 |
| 86 | + - '.': 해당 위치에서만 분기, 이후는 다시 좁혀짐 |
| 87 | + - 브루트포스: 모든 단어를 처음부터 끝까지 비교 |
| 88 | +
|
| 89 | +7. 시간복잡도: |
| 90 | + - addWord: O(L) - L은 단어 길이 |
| 91 | + - search: O(L) ~ O(26^m) - m은 '.' 개수 (보통 적음) |
| 92 | +
|
| 93 | +8. 공간복잡도: O(N × L) - 모든 단어의 문자 저장 |
| 94 | +
|
| 95 | +9. 구현 포인트: |
| 96 | + - TrieNode 클래스 분리 → 가독성 향상 |
| 97 | + - _dfs 재귀로 '.' 처리 → 모든 자식 탐색 |
| 98 | + - is_end_word로 단어 끝 표시 → 접두사와 구분 |
| 99 | +""" |
0 commit comments