본문으로 바로가기

단어 변환(Level 3)

문제

전체 문제 보기

 

코딩테스트 연습 - 단어 변환

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

programmers.co.kr

접근법

각 단어를 하나의 노드로 간주한다면 "변환 가능한 단어"라는 말은 "이동 가능한 노드"라는 개념으로 볼 수 있다. 결국 이 문제는 그래프에서 길 찾기 문제와 본질적으로 같다. 다만, 노드가 알파벳으로 구성된 단어라는 점이 일반적인 길 찾기와의 차이점이다. 때문에 이 문제에서는 임의의 두 노드가 서로 이동 가능한 노드인지를 구분하기 위한 별도의 방법이 필요하다. 문제의 설명에서 두 단어의 알파벳이 하나만 다를 경우 변환 가능하다고 했다. 즉, 알파벳 하나만 다른 노드는 이동 가능한 노드라는 말이다. 이 부분을 추가적으로 구현한다면 기본적인 BFS와 동일하게 풀 수 있다.

 

BFS를 활용한 길찾기에 대해서 더 알고 싶다면 아래의 글을 참고하자.

 

choisb/Study-DataStructure

자료구조 학습용 저장소. Contribute to choisb/Study-DataStructure development by creating an account on GitHub.

github.com

 

4장 인공지능 - ② 길 찾기 알고리즘

4장 인공지능 - ② 길 찾기 알고리즘 인공지능(AI, Artificial Intelligence)알고리즘은 게임에서 컴퓨터가 제어하는 오브젝트의 행위를 결정하는 데 사용한다. 4장에서는 3가지의 유용한 AI 게임 테크닉

dev-sbee.tistory.com

구현

두 노드가 서로 인접한 노드인지 확인하기 위해서 IsExchangeable 함수를 주목해서 보자. 이 외의 다른 부분들은 일반적은 BFS 구현과 동일하기 때문에 자세한 설명은 주석으로 대체한다.

#include <string>
#include <vector>
#include <queue>

using namespace std;

// Node는 문자와 깊이를 가진다.
struct Node{
    Node(const string& str)
    :depth(0)
    ,word(str)
    {
    }

    string word;
    int depth = 0;
};

// 두 개의 문자가 바꿀 수 있는 관계인지 반환 
bool IsExchangeable(const string& left, const string& right)
{
    int diff = 0;
    for(int i = 0; i < left.size(); ++i)
        if(left[i] != right[i])
            diff++;

    return diff == 1;
}

// bfs 실행. 탐색 깊이를 반환
int bfs(const string& begin, const string& target, const vector<Node*>& nodes)
{  
    queue<const Node*> q;
    const Node* curNode;

    // queue에 초기값 삽입(begin 과 바로 인접한 노드들)
    for(auto node : nodes)
    {
        if(IsExchangeable(begin, node->word))
        {
            // 첫 탐색이기 때문에 깊이는 모두 1이다
            node->depth = 1;
            q.emplace(node);
        }
    }

    // 탐색 시작
    // 큐가 비었다는 것은 모든 경우의 수를 다 따져봤지만 변환할 방법이 없다는 뜻.
    while(!q.empty())
    {
        // 큐의 첫번째 노드를 꺼내서 확인한다.
        curNode = q.front();
        q.pop();

        // 목표를 찾았다면 curNode의 깊이를 반환한다.
        if(curNode->word.compare(target) == 0)
            return curNode->depth;

        // 현재 노드에서 변환가능하며 큐에 삽입된적 없는 노드를 모두 큐에 삽입한다.
        for(auto node : nodes)
        {
            if(IsExchangeable(curNode->word, node->word) && node->depth == 0)
            {
                // 새로 큐에 삽입되는 노드의 깊이는 현재 노드의 다음 노드이기 때문에
                // 현재 노드 깊이의 +1 값이다.
                node->depth = curNode->depth + 1;
                q.emplace(node);
            }
        }
    }    
    return 0;
}

int solution(string begin, string target, vector<string> words) 
{     
    int answer = 0;
    vector<Node*> nodes;

    // words를 활용해서 nodes 벡터 생성
    for(auto word : words)
    {
        Node* nNode = new Node(word);
        nodes.emplace_back(nNode);
    }

    // bfs 결과값 반환
    answer = bfs(begin, target, nodes);

    // 동적할당한 node들 삭제
    for(auto node : nodes)
    {
        delete node;
    }

    return answer;
}