[프로그래머스] 단어 변환

DFSBFSDijkstra
avatar
2025.03.19
·
5 min read

문제 링크

문제 설명

두 개의 단어 begin, target과 단어의 집합 words가 있습니다. 아래와 같은 규칙을 이용하여 begin에서 target으로 변환하는 가장 짧은 변환 과정을 찾으려고 합니다.

1. 한 번에 한 개의 알파벳만 바꿀 수 있습니다.
2. words에 있는 단어로만 변환할 수 있습니다.

예를 들어 begin이 "hit", target가 "cog", words가 ["hot","dot","dog","lot","log","cog"]라면 "hit" -> "hot" -> "dot" -> "dog" -> "cog"와 같이 4단계를 거쳐 변환할 수 있습니다.

두 개의 단어 begin, target과 단어의 집합 words가 매개변수로 주어질 때, 최소 몇 단계의 과정을 거쳐 begin을 target으로 변환할 수 있는지 return 하도록 solution 함수를 작성해주세요.

제한사항

  • 각 단어는 알파벳 소문자로만 이루어져 있습니다.

  • 각 단어의 길이는 3 이상 10 이하이며 모든 단어의 길이는 같습니다.

  • words에는 3개 이상 50개 이하의 단어가 있으며 중복되는 단어는 없습니다.

  • begin과 target은 같지 않습니다.

  • 변환할 수 없는 경우에는 0를 return 합니다.


문제 이해

이 문제는 한 번에 한 개의 알파벳만 바꿀 수 있고, words 목록 내 단어만 사용할 수 있을 때 최소 변환 횟수를 구하는 문제이다.

문제에서 중요한 단서

  • 가장 짧은 변환 과정 → 최단 경로를 구하는 문제이므로 DFS, BFS, 다익스트라를 시도해 볼 수 있음.

문제 해결 접근 방법

  1. 모든 경우를 탐색하는 가장 기본적인 방식인 DFS를 적용해 볼 수 있다.

    func solution(_ begin:String, _ target:String, _ words:[String]) -> Int {
        var minSteps = Int.max
        var visited = Set<String>()
    
        func dfs(_ current: String, _ steps: Int) {
            if current == target {
                minSteps = min(minSteps, steps)
                return
            }
            
            for word in words where !visited.contains(word) {
                let isOneWordDiff = zip(current, word)
                    .filter { $0 != $1 }
                    .count == 1
                if isOneWordDiff {
                    visited.insert(word)
                    dfs(word, steps + 1)
                    visited.remove(word)
                }
            }
        }
    
        dfs(begin, 0)
        return minSteps == Int.max ? 0 : minSteps
    }

    DFS 자체는 최단 경로를 보장하지 않기 때문에 아래와 같은 조건이 추가되어야 한다.

    • 현재 찾은 최단 경로보다 긴 경로는 탐색하지 않음.

    • 이미 더 짧은 경로를 찾았다면, 긴 경로를 탐색할 필요가 없음.

    • 따라서, minSteps 값을 사용하여 불필요한 탐색을 Pruning 해야 함.

  2. BFS는 DFS보다 효율적으로 불필요한 탐색을 제거할 수 있다.

    func solution(_ begin:String, _ target:String, _ words:[String]) -> Int {
        var queue = [(begin, 0)]
        var visited = Set<String>()
        
        while !queue.isEmpty {
            let (current, steps) = queue.removeFirst()
            if current == target { return steps }
            
            for word in words {
                let isOneWordDiff = zip(current, word)
                    .filter { $0 != $1 }
                    .count == 1
                if isOneWordDiff && !visited.contains(word) {
                    queue.append((word, steps + 1))
                    visited.insert(word)
                }
            }
        }
        
        return 0
    }
  3. 대표적인 최단 경로 탐색 알고리즘인 다익스트라를 적용해볼 수도 있다.

    func solution(_ begin:String, _ target:String, _ words:[String]) -> Int {
        var queue = [(begin, 0)]
        var distance = [String: Int]()
        
        for word in words {
            distance[word] = Int.max
        }
        distance[begin] = 0
    
        while !queue.isEmpty {
            queue.sort { $0.1 < $1.1 }
            let (current, cost) = queue.removeFirst()
            
            if current == target { return cost }
    
            for word in words where cost + 1 < (distance[word] ?? Int.max) {
                let isOneWordDiff = zip(current, word)
                    .filter { $0 != $1 }
                    .count == 1
                if isOneWordDiff {
                    distance[word] = cost + 1
                    queue.append((word, cost + 1))
                }
            }
        }
    
        return 0
    }

    하지만, 모든 단어 간 변환의 비용이 동일하므로 굳이 다익스트라를 적용할 필요는 없다.







- 컬렉션 아티클