https://leetcode.com/problems/word-ladder/description/
ํ๋ก๊ทธ๋๋จธ์ค์ ์๋ '๋จ์ด ๋ณํ' ๋ฌธ์ ์ ๋๊ฐ์ ๋ฌธ์ ์ด๋ค. ๋ค๋ง, ํ๋ก๊ทธ๋๋จธ์ค์ ์๋ ๋ฌธ์ ๋ ๋จ์ด์ ๊ฐ์๊ฐ ์ต๋ 50๊ฐ๋ก, ์ต๋ ๋จ์ด ๊ฐ์๊ฐ 5000๊ฐ์ธ LeetCode์๋ ์ฐจ์ด๊ฐ ์๋ค.
ํ๋ก๊ทธ๋๋จธ์ค์์ ๋จผ์ ํ๊ณ , ๋จ์ด ๊ฐ์๊ฐ ํฐ LeetCode์์ ์ ์ ์ต์ ํ๋ฅผ ํ๋ฉด์ ํ์๋ค.
๋ฌธ์
(ํ๊ตญ์ด ๋ฒ์ญ)
๋จ์ด ์ฌ์ ์ธ `wordList`๋ฅผ ์ด์ฉํด `beginWord`์์ `endWord`๊น์ง ๋ณํํ๋ ์ํ์ค๋ ๋ค์๊ณผ ๊ฐ๋ค.
`beginWord -> S1 -> S2 -> ... -> Sk`
- ๋ชจ๋ ์ธ์ ํ๋ ๋จ์ด ์์ ํ ๊ธ์๋ง ๋ค๋ฅด๋ค.
- ๋ชจ๋ ๋จ์ด๋ `wordList`์ ์ํ๋ค. `beginWord`๋ `wordList`์ ์ํ์ง ์์๋ ๋๋ค.
- `Sk == endWord`
๋ ๋จ์ด์ธ `beginWord`์ `endWord`, ๋จ์ด ์ฌ์ ์ธ `wordList`๊ฐ ์ฃผ์ด์ก์ ๋, `beginWord` ์์ `endWord` ๋ก ๋ณํํ๋ ๊ฐ์ฅ ์งง์ ๋ณํ ์ํ์ค์ ๋จ์ด ์๋ฅผ ๋ฆฌํดํ๋ผ. ์ํ์ค๊ฐ ์กด์ฌํ์ง ์๋๋ค๋ฉด 0์ ๋ฆฌํดํ๋ผ.
์ ํ์ฌํญ
- 1 <= beginWord.length <= 10
- endWord.length == beginWord.length
- 1 <= wordList.length <= 5000
- wordList[i].length == beginWord.length
- beginWord, endWord, wordList[i]๋ ์ํ๋ฒณ ์๋ฌธ์๋ก๋ง ์ด๋ฃจ์ด์ ธ ์๋ค.
- beginWord != endWord
- wordList์ ์๋ ๋ชจ๋ ๋จ์ด๋ ์ค๋ณต๋์ด ์์ง ์๋ค.
์์1
์ ๋ ฅ
beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log","cog"]
์ถ๋ ฅ
5
์์2
์ ๋ ฅ
beginWord = "hit", endWord = "cog", wordList = ["hot","dot","dog","lot","log"]
์ถ๋ ฅ
0
ํ์ด
BFS ์ ํ ์ด์
1. DFS์์ ๋น๊ต
- BFS(๋๋น ์ฐ์ ํ์)์ ํน์ ๋ ธ๋์์ ๋ค๋ฅธ ๋ ธ๋๊น์ง์ ์ต๋จ ๊ฒฝ๋ก๋ฅผ ์ฐพ๋ ๋ฐ ์ ํฉํ๋ค. ๊ฐ๋ฅํ ๋ชจ๋ ๊ฒฝ๋ก๋ฅผ ๋จ๊ณ๋ณ๋ก ๋์์ ํ์ํ๊ณ , ๋ชฉํ ๋ ธ๋์ ๋๋ฌํ๋ ์๊ฐ ์ต๋จ ๊ฒฝ๋ก๊ฐ ๋๋ค.
- DFS(๊น์ด ์ฐ์ ํ์)์ ๊ฐ๋ฅํ ๋ชจ๋ ๊ฒฝ๋ก๋ฅผ ๋๊น์ง ํ์ํ ๋ค์ ์ต๋จ ๊ฒฝ๋ก๋ฅผ ์ ์ ์๋ค. ๊น์ด๊ฐ ๊น์์๋ก ๋ถํ์ํ ๊ฒฝ๋ก ํ์์ด ๋ง์์ง๊ฒ ๋๋ค.
2. ์๊ฐ๋ณต์ก๋
(v : ์ ์ ์ = ๋จ์ด ๋ชฉ๋ก ๊ธธ์ด, E : ๊ฐ์ ์ = ๋ณํ ๊ฐ๋ฅํ ๋จ์ด ์)
- BFS : O(V + E)
- DFS : O(V + E)
- DFS๋ ์ต๋จ ๊ฒฝ๋ก๋ฅผ ์ฐพ๊ธฐ ์ํด ๋ชจ๋ ๊ฒฝ๋ก๋ฅผ ํ์ํ๋ฏ๋ก, ๊น์ด๊ฐ ๊น์์๋ก ๋นํจ์จ์ ์ด๋ค.
์ฝ๋
from collections import deque, defaultdict
class Solution:
def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
wordList = set(wordList)
queue = deque([beginWord])
visited = set([beginWord])
answer = 1
while queue:
size = len(queue)
for _ in range(size):
word = queue.popleft()
if word == endWord:
return answer
for nextWord in wordList:
if nextWord not in visited and self.check(word, nextWord):
queue.append(nextWord)
visited.add(nextWord)
answer += 1
return 0
def check(self, s1: str, s2: str) -> bool:
count = 0
for i in range(len(s1)):
if s1[i] != s2[i]:
count += 1
return count == 1
๊ฒฐ๊ณผ
- ์ด `O(N^2∗M)`์ ์๊ฐ ๋ณต์ก๋๋ฅผ ๊ฐ์ง ์ฝ๋
- 8500ms๋์ ์๊ฐ์ด ๋์จ๋ค. ์๊ฐ ๋ณต์ก๋๋ฅผ ์ค์ผ ํ์๊ฐ ์๋ค.
์ต์ ํ1 : ํจํด ๋งค์นญ
AS-IS : ๋ฌธ์์ด ๋น๊ต
def check(self, s1: str, s2: str) -> bool:
count = 0
for i in range(len(s1)):
if s1[i] != s2[i]:
count += 1
return count == 1
- ๋ ๋จ์ด๋ฅผ ํ ๊ธ์์ฉ ์ฐจ๋ก๋ก ๋น๊ตํ์ฌ, ๋ค๋ฅธ ๊ธ์๊ฐ 1๊ธ์์ธ์ง ํ์ธ
- ์๊ฐ ๋ณต์ก๋
- ํ ๋จ๊ณ์์์ ์๊ฐ ๋ณต์ก๋ : `O(N * L)` (N: ๋จ์ด ๋ชฉ๋ก ๊ธธ์ด, L : ๋จ์ด ๊ธธ์ด)
- ์ ์ฒด ์๊ฐ ๋ณต์ก๋ : `O(N^2 * L)` (์ฌ๋ฌ ๋จ๊ณ(N)์ ๋ํด ๋ณํ ๊ฐ๋ฅํ์ง ํ์ธ)
TO-BE : ํจํด ๋งค์นญ
def build_Pattern_map(self, wordList: list) -> dict:
pattern_map = defaultdict(list)
for word in wordList:
for i in range(len(word)):
pattern = word[:i] + "*" + word[i + 1 :]
pattern_map[pattern].append(word)
return pattern_map
- ๊ฐ ๋จ์ด์ ๋ํด ๊ฐ๋ฅํ ํจํด์ ๋ฏธ๋ฆฌ ์์ฑํ๊ณ , ํจํด์์ ๊ฐ๋ฅํ ๋จ์ด๋ฅผ ์ฐพ์
- ๋จ์ด์์ ํ ๊ธ์๋ฅผ *๋ก ์นํํ ํจํด์ Dictionary์ ์ ์ฅ
- key : ํจํด
- value : ํจํด์ ํด๋นํ๋ ๋จ์ด
- ex) *ot:[hot, dot]
- ์๊ฐ ๋ณต์ก๋
- ํจํด ์์ฑ : `O(N * L)` (N: ๋จ์ด ๋ชฉ๋ก ๊ธธ์ด, L : ๋จ์ด ๊ธธ์ด)
- ์ ์ฒด ์๊ฐ ๋ณต์ก๋ : `O(N * L)` (์ฌ๋ฌ ๋จ๊ณ(N)์ ๋ํด ๋จ์ด ๊ธธ์ด(L)๋งํผ ํ์ธ)
์ฝ๋
from collections import deque, defaultdict
class Solution:
def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
pattern_map = defaultdict(list) # ๋ชจ๋ ๋จ์ด์ ํจํด์ ์ ์ฅํ ํด์๋งต ์์ฑ
for word in wordList:
for i in range(len(beginWord)):
pattern = word[:i] + '*' + word[i+1:]
pattern_map[pattern].append(word)
# BFS ํ์
queue = deque([beginWord])
visited = set([beginWord])
answer = 1
while queue:
for _ in range(len(queue)):
curWord = queue.popleft()
if curWord == endWord:
return answer
for i in range(len(curWord)):
pattern = curWord[:i] + '*' + curWord[i+1:]
for nextWord in pattern_map[pattern]:
if nextWord not in visited:
queue.append(nextWord)
visited.add(nextWord)
answer += 1
return 0
๊ฒฐ๊ณผ
- ์ด `O(N∗M)`์ ์๊ฐ ๋ณต์ก๋๋ฅผ ๊ฐ์ง ์ฝ๋
- 120ms๋์ ์๊ฐ์ด ๋์จ๋ค. ๋ง์ด ๊ฐ์ ๋์๋ค.
์ต์ ํ2 : ์๋ฐฉํฅ BFS
- ์์์ (beginWord)๊ณผ ๋ชฉํ ์ง์ (endWord)์์ ๋์์ ํ์์ ์์ํ์ฌ, ๋ ํ์์ด ๋ง๋๋ ์ง์ ์ ์ฐพ๋๋ค.
- ํ์ ํ๋ฅผ ๋ ๊ฐ ์ฐ์ง๋ง, ํ์ ๋ฒ์๋ฅผ ๋ฐ์ผ๋ก ์ค์ผ ์ ์๋ค.
- ์ ์ฒด ์๊ฐ ๋ณต์ก๋ : `O((N * L) / 2`
์ฝ๋
from collections import deque, defaultdict
class Solution:
def ladderLength(self, beginWord: str, endWord: str, wordList: List[str]) -> int:
if endWord not in wordList:
return 0
pattern_map = defaultdict(list) # ๋ชจ๋ ๋จ์ด์ ํจํด์ ์ ์ฅํ ํด์๋งต ์์ฑ
for word in wordList:
for i in range(len(beginWord)):
pattern = word[:i] + '*' + word[i+1:]
pattern_map[pattern].append(word)
# ์๋ฐฉํฅ BFS ํ์
start_queue = deque([beginWord])
end_queue = deque([endWord])
start_visited = set([beginWord])
end_visited = set([endWord])
answer = 1
while start_queue and end_queue:
if len(start_queue) > len(end_queue):
start_queue, end_queue = end_queue, start_queue
start_visited, end_visited = end_visited, start_visited
for _ in range(len(start_queue)):
curWord = start_queue.popleft()
for i in range(len(curWord)):
pattern = curWord[:i] + '*' + curWord[i+1:]
for nextWord in pattern_map[pattern]:
if nextWord in end_visited:
return answer + 1
if nextWord not in start_visited:
start_queue.append(nextWord)
start_visited.add(nextWord)
answer += 1
return 0
๊ฒฐ๊ณผ
- ์ด `O(N∗M/2)`์ ์๊ฐ ๋ณต์ก๋๋ฅผ ๊ฐ์ง ์ฝ๋
- 80ms๋์ ์๊ฐ์ด ๋์จ๋ค. ์กฐ๊ธ ๋ ๊ฐ์ ๋์๋ค.
๊ฒฐ๋ก
ํจํด ๋งค์นญ๊ณผ ์๋ฐฉํฅ BFS์ ์ฌ์ฉํ๋ฉด, ๋น ๋ฅด๊ฒ ์ต์ ๊ธธ์ด๋ฅผ ์ฐพ์ ์ ์๋ค.