|
| 1 | +/* |
| 2 | +풀이 |
| 3 | +- Trie 자료구조와 2차원 배열에서의 DFS & Backtracking을 사용하여 풀이할 수 있습니다 |
| 4 | +Big O |
| 5 | +- M, N: 주어진 2차원 배열 board의 행, 열 크기 |
| 6 | +- Max(W): 주어진 배열 words 중에서 가장 길이가 긴 단어의 길이 |
| 7 | +- Sum(W): 주어진 배열 words에 포함된 모든 단어의 길이의 총합 |
| 8 | +- Time complexity: O(Sum(W) + MN * Max(W)) |
| 9 | + - building trie: O(Sum(W)) |
| 10 | + - 각 단어의 모든 문자를 한 번씩 조회합니다 |
| 11 | + - 반복문: O(MN * Max(W)) |
| 12 | + - 행, 열에 대한 반복: O(MN) |
| 13 | + - dfs: O(Max(W)) |
| 14 | + - 한 좌표에 대해 dfs 함수를 trie의 최대 깊이, 즉 O(Max(W))만큼 호출합니다 |
| 15 | +- Space complexity: O(Sum(W) + Max(W)) |
| 16 | + - building trie: O(Sum(W)) at worst |
| 17 | + - dfs: O(Max(W)) |
| 18 | + - 재귀 호출 스택의 깊이를 고려해야 합니다 |
| 19 | +*/ |
| 20 | + |
| 21 | +func findWords(board [][]byte, words []string) []string { |
| 22 | + // building trie |
| 23 | + root := &trieNode{} |
| 24 | + for _, w := range words { |
| 25 | + root.add(w) |
| 26 | + } |
| 27 | + |
| 28 | + m := len(board) |
| 29 | + n := len(board[0]) |
| 30 | + res := make([]string, 0, len(words)) // create result |
| 31 | + for r := 0; r < m; r++ { |
| 32 | + for c := 0; c < n; c++ { |
| 33 | + if len(res) == len(words) { // early break if we found all the words |
| 34 | + break |
| 35 | + } |
| 36 | + if root.children[idx(board[r][c])] != nil { // dfs if you found starting letter of any words |
| 37 | + dfs(r, c, &board, root, &res) |
| 38 | + } |
| 39 | + } |
| 40 | + } |
| 41 | + |
| 42 | + return res |
| 43 | +} |
| 44 | + |
| 45 | +// ----- Implementation of TrieNode ----- |
| 46 | +type trieNode struct { |
| 47 | + children [26]*trieNode |
| 48 | + word string |
| 49 | +} |
| 50 | + |
| 51 | +func (t *trieNode) add(w string) { |
| 52 | + for i, c := range w { |
| 53 | + if t.children[c-'a'] == nil { // create new child node if there wasn't |
| 54 | + t.children[c-'a'] = &trieNode{} |
| 55 | + } |
| 56 | + t = t.children[c-'a'] |
| 57 | + if i == len(w)-1 { |
| 58 | + t.word = w |
| 59 | + } |
| 60 | + } |
| 61 | +} |
| 62 | + |
| 63 | +// ----- Dfs ----- |
| 64 | +func dfs(r, c int, board *[][]byte, parentNode *trieNode, res *[]string) { |
| 65 | + currLetter := (*board)[r][c] |
| 66 | + currNode := parentNode.children[idx(currLetter)] |
| 67 | + // if current trie node represents some word, then append it to the result |
| 68 | + if currNode.word != "" { |
| 69 | + *res = append(*res, currNode.word) |
| 70 | + currNode.word = "" // prevent duplicate words into the result |
| 71 | + } |
| 72 | + // mark current cell as visited |
| 73 | + (*board)[r][c] = '#' |
| 74 | + // search for the 4 directions |
| 75 | + m := len(*board) |
| 76 | + n := len((*board)[0]) |
| 77 | + dr := []int{0, 0, 1, -1} |
| 78 | + dc := []int{1, -1, 0, 0} |
| 79 | + for i := 0; i < 4; i++ { |
| 80 | + nr := r + dr[i] |
| 81 | + nc := c + dc[i] |
| 82 | + if !(0 <= nr && nr < m) || !(0 <= nc && nc < n) { // skip the invalid coordinates |
| 83 | + continue |
| 84 | + } |
| 85 | + if (*board)[nr][nc] == '#' { // skip the visited cell |
| 86 | + continue |
| 87 | + } |
| 88 | + if currNode.children[idx((*board)[nr][nc])] != nil { |
| 89 | + dfs(nr, nc, board, currNode, res) |
| 90 | + } |
| 91 | + } |
| 92 | + // restore the cell |
| 93 | + (*board)[r][c] = currLetter |
| 94 | +} |
| 95 | + |
| 96 | +// ----- Helper ----- |
| 97 | +func idx(b byte) int { |
| 98 | + return int(b - 'a') |
| 99 | +} |
0 commit comments