DEVELOP
article thumbnail

단어 변환 #43163

더보기

문제 설명

두 개의 단어 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 합니다.
입출력 예begintargetwordsreturn
"hit" "cog" ["hot", "dot", "dog", "lot", "log", "cog"] 4
"hit" "cog" ["hot", "dot", "dog", "lot", "log"] 0
입출력 예 설명

예제 #1
문제에 나온 예와 같습니다.

예제 #2
target인 "cog"는 words 안에 없기 때문에 변환할 수 없습니다.

나의 풀이 

function solution(begin, target, words) {
  if (!words.includes(target)) return 0;

  words.push(begin);
  const size = words.length;
  const length = begin.length; // 각 단어들의 길이
  const counting = Array(size).fill(0); // 방문 배열 겸, 지금까지 몇 번 교체를 하였는지 저장하는 배열

  const arr = Array.from(Array(size), () => Array()); // 연결관계를 나타내는 배열 (인접리스트)

  /* 인접리스트 생성하기 */
  for (let i = 0; i < size; i++) {
    for (let j = 0; j < size; j++) {
      let count = 0;
      for (let k = 0; k < length; k++) {
        if (i !== j && words[j] !== begin && words[i][k] === words[j][k])
          count++;
      }
      if (count === length - 1) {
        arr[i].push(words[j]);
      }
    }
  }
  /* 위 코드를 실행한 뒤 arr은 아래와 같다. 
    [
      [ 'dot', 'lot' ],
      [ 'hot', 'dog', 'lot' ],
      [ 'dot', 'log', 'cog' ],
      [ 'hot', 'dot', 'log' ],
      [ 'dog', 'lot', 'cog' ],
      [ 'dog', 'log' ],
      [ 'hot' ]
    ]
  */

  const queue = [begin];

  while (true) {
    if (queue.length === 0) break;
    const curWord = queue.shift();
    if (curWord === target) break;
    const linked = arr[words.indexOf(curWord)];

    for (const word of linked) {
      const cnt = counting[words.indexOf(curWord)];
      if (counting[words.indexOf(word)] === 0) {
        counting[words.indexOf(word)] = cnt + 1;
        queue.push(word);
      }
    }
  }

  /*
    위 코드를 실행하고 난 뒤 counting 배열은 아래와 같다.
    [ 1, 2, 3, 2, 3, 4, 0 ]
    위 배열의 인덱스는 words 배열의 인덱스와 매칭된다.
    각 숫자의 뜻은 "hit" 부터 시작하여 각 단어까지 몇번의 교체가 이루어졌는지를 의미한다. 
    ex) hit -> dot 이 되려면 2번의 교체가 이루어져야 함 
  */
  return counting[words.indexOf(target)];
}

 

  • BFS 개념을 공부하면서, 인접리스트를 이용해서 푼 문제가 생각나서 처음부터 접근을 그런 식으로 했던 것 같다. 
  • 그래서 너무 비효율적인 코드(무려 3중for문. ... )임을 알지만, 너비 우선 탐색을 하기 전에, 인접리스트를 만들어서 해결하고자 했다. 
  • begin도 비교하여 인접리스트에 반영하기 위해 begin words에 추가하여 같이 비교를 해주었다.
  • 그리고 counting 배열은 visited 배열 겸, 지금까지 몇번 교체를 했는지 저장해주는 배열로 활용했다. 
  • 그러고나서 BFS를 돌려주었다. 

다른 풀이를 참고하여 수정한 풀이

function solution(begin, target, words) {
  // 다른 사람의 풀이를 참고한 풀이
  // 연결리스트 따로 만들지 않고, isAvailable 함수를 두어 두 단어가 교체 가능한지 여부를 판단

  if (!words.includes(target)) return 0;
  const counting = Array(words.length).fill(0); // 방문 배열 겸, 지금까지 몇 번 교체를 하였는지 저장하는 배열
  const queue = [begin];

  while (true) {
    if (queue.length === 0) break;
    const curWord = queue.shift();

    if (curWord === target) break;

    const cnt = counting[words.indexOf(curWord)] ?? 0;

    for (let i = 0; i < words.length; i++) {
      if (isAvailable(curWord, words[i]) && counting[i] === 0) {
        counting[i] = cnt + 1;
        queue.push(words[i]);
      }
    }
  }
  // 두 단어가 교체 가능한 단어인지 비교해서 리턴하는 함수
  function isAvailable(standard, compare) {
    let differntCount = 0; // 다른 문자의 수, 1보다 크면 두 단어는 교체 불가
    for (let i = 0; i < standard.length; i++) {
      if (standard[i] !== compare[i]) differntCount++;
      if (differntCount > 1) return false;
    }
    return true;
  }

  return counting[words.indexOf(target)];
}
  • 인접리스트에만 포커스를 맞추고 있어서 다른 방식이 안떠올랐었는데, 위 코드처럼 두 단어를 비교하는 함수를 따로 정의하여 T/F 값만 비교하니까 훨씬 깔끔하고 효율적인 코드가 되었다 ! 
  • 나머지 코드는 비슷하게 풀었다. 

입력값의 크기가 커질수록 차이가 많이 나는 듯 하다.

profile

DEVELOP

@JUNGY00N