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์™€ DFS๋ฅผ ์‚ฌ์šฉํ•ด์•ผ ํ•œ๋‹ค.
๋‹จ์–ด ๋ชฉ๋ก์˜ ๊ธธ์ด๊ฐ€ `1 โ‰ค len(wordList) โ‰ค 5,000` ์œผ๋กœ ํšจ์œจ์ ์ธ ํƒ์ƒ‰ ์•Œ๊ณ ๋ฆฌ์ฆ˜์ด ํ•„์š”ํ•˜๋‹ค.

 

 

 

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์„ ์‚ฌ์šฉํ•˜๋ฉด, ๋น ๋ฅด๊ฒŒ ์ตœ์†Œ ๊ธธ์ด๋ฅผ ์ฐพ์„ ์ˆ˜ ์žˆ๋‹ค.

giraffe_