본문 바로가기
개발자 일기/알고리즘 공부

[프로그래머스 JS] 카드 뭉치

by MS_developer 2023. 6. 24.

📖 문제 설명

코니는 영어 단어가 적힌 카드 뭉치 두 개를 선물로 받았습니다. 코니는 다음과 같은 규칙으로 카드에 적힌 단어들을 사용해 원하는 순서의 단어 배열을 만들 수 있는지 알고 싶습니다.

  • 원하는 카드 뭉치에서 카드를 순서대로 한 장씩 사용합니다.
  • 한 번 사용한 카드는 다시 사용할 수 없습니다.
  • 카드를 사용하지 않고 다음 카드로 넘어갈 수 없습니다.
  • 기존에 주어진 카드 뭉치의 단어 순서는 바꿀 수 없습니다.

예를 들어 첫 번째 카드 뭉치에 순서대로 ["i", "drink", "water"], 두 번째 카드 뭉치에 순서대로 ["want", "to"]가 적혀있을 때 ["i", "want", "to", "drink", "water"] 순서의 단어 배열을 만들려고 한다면 첫 번째 카드 뭉치에서 "i"를 사용한 후 두 번째 카드 뭉치에서 "want"와 "to"를 사용하고 첫 번째 카드뭉치에 "drink"와 "water"를 차례대로 사용하면 원하는 순서의 단어 배열을 만들 수 있습니다.

문자열로 이루어진 배열 cards1cards2와 원하는 단어 배열 goal이 매개변수로 주어질 때, cards1과 cards2에 적힌 단어들로 goal를 만들 수 있다면 "Yes"를, 만들 수 없다면 "No"를 return하는 solution 함수를 완성해주세요.


🚫 제한 사항

  • 1 ≤ cards1의 길이, cards2의 길이 ≤ 10
    • 1 ≤ cards1[i]의 길이, cards2[i]의 길이 ≤ 10
    • cards1과 cards2에는 서로 다른 단어만 존재합니다.
  • 2 ≤ goal의 길이 ≤ cards1의 길이 + cards2의 길이
    • 1 ≤ goal[i]의 길이 ≤ 10
    • goal의 원소는 cards1과 cards2의 원소들로만 이루어져 있습니다.
  • cards1, cards2, goal의 문자열들은 모두 알파벳 소문자로만 이루어져 있습니다.

💾 입출력 예시

 

keymap targets goal result
["i", "drink", "water"] ["want", "to"] ["i", "want", "to", "drink", "water"] "Yes"
["i", "water", "drink"] ["want", "to"] ["i", "want", "to", "drink", "water"] "No"

 


⌨️ 나의 풀이 (코드)

 

먼저 의사 코드를 통해 구현해야 하는 로직에 대해 정리해 보았다.

 

1. goal을 순회, 첫 번째 요소 (문자열)이 cards1 또는 cards2의 첫 번째 요소와 일치하는지 확인
2. 불가능하다면 순회를 종료, "No"를 return
3. 가능하다면 다음 뭉치로 이동, 반복 => 단, 사용했던 카드는 일치할 때 해당 배열에서 삭제

 

비교적 단순한 문제라고 생각해 바로 코드로 옮겨 보았다.

 

function solution(cards1, cards2, goal) {
  goal.forEach((word, idx) => {
    if (word === cards1[0]) {
      cards1.shift();
    } else if (word === cards2[0]) {
      cards2.shift();
    } else {
      return "No";
    }
  });

  return "Yes";
}

 

어째선지 기본 테스트를 통과하지 못했다.

 

로직 자체의 구성에 문제가 없어 보였고, 덱에서 한 장씩의 카드를 맞출 때마다 제거한다는 개념도 잘 따랐다고 생각해서 혼란이 왔다. 맞왜틀?

 

짧은 검색을 통해 원인을 알게되었다. MDN 문서의 forEach() 메서드에 대한 설명에서 알 수 있듯, forEach() 메서드는 항상 undefined 값을 반환하기 때문에 return ~~ 라고 입력해도 값을 올바르게 반환할 수 없다. 즉, 코드에서 적었던 것처럼 모든 카드 덱과 단어가 일치하지 않는 경우더라도 "No"를 반환하고 함수를 종료하지 않고, 계속 순회가 진행된다.

 

이에 대한 해결책으로 try catch 문을 통해 강제로 throw 에러로 순회를 종료할 수는 있지만, 원했던 방식이 아니었기 때문에 코드를 다른 방식으로 수정했다.

 

function solution(cards1, cards2, goal) {
  let wordNotFound = false;
  goal.forEach((word, idx) => {
    if (word === cards1[0]) {
      cards1.shift();
    } else if (word === cards2[0]) {
      cards2.shift();
    } else {
      wordNotFound = true;
    }
  });

  if (wordNotFound === true) {
    return "No";
  } else {
    return "Yes";
  }
}

 

 

wordNotFound 라는 변수를 선언하고 boolean 값을 할당해, 만약 두 카드 덱에서 일치하는 단어를 찾을 수 없다면 해당 변수의 값을 변경하는 방식으로 코드를 수정했다. 이후 테스트는 모두 잘 통과되었다.


📝 Note

 

try catch 문 없이도 문제를 해결하는 간단한 방법이 있었다. 그냥 for 문을 사용하는 것!

 

function solution(cards1, cards2, goal) {

    for(const s of goal) {

        if(cards1[0] == s) {
            cards1.shift();
        } else if(cards2[0] == s) {
            cards2.shift();
        } else {
            return "No"
        }
    }

    return "Yes";
}

 

해당 코드가 내 최초 의도와 더 들어맞는 것 같아 참고가 되었다.

댓글