DEVELOP
article thumbnail

2019 카카오 개발자 겨울 인턴십 | 크레인 인형뽑기 게임 #64061

 

프로그래머스

코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

programmers.co.kr

더보기

문제 설명

게임개발자인 "죠르디"는 크레인 인형뽑기 기계를 모바일 게임으로 만들려고 합니다.
"죠르디"는 게임의 재미를 높이기 위해 화면 구성과 규칙을 다음과 같이 게임 로직에 반영하려고 합니다.

게임 화면은 "1 x 1" 크기의 칸들로 이루어진 "N x N" 크기의 정사각 격자이며 위쪽에는 크레인이 있고 오른쪽에는 바구니가 있습니다. (위 그림은 "5 x 5" 크기의 예시입니다). 각 격자 칸에는 다양한 인형이 들어 있으며 인형이 없는 칸은 빈칸입니다. 모든 인형은 "1 x 1" 크기의 격자 한 칸을 차지하며 격자의 가장 아래 칸부터 차곡차곡 쌓여 있습니다. 게임 사용자는 크레인을 좌우로 움직여서 멈춘 위치에서 가장 위에 있는 인형을 집어 올릴 수 있습니다. 집어 올린 인형은 바구니에 쌓이게 되는 데, 이때 바구니의 가장 아래 칸부터 인형이 순서대로 쌓이게 됩니다. 다음 그림은 [1번, 5번, 3번] 위치에서 순서대로 인형을 집어 올려 바구니에 담은 모습입니다.

만약 같은 모양의 인형 두 개가 바구니에 연속해서 쌓이게 되면 두 인형은 터뜨려지면서 바구니에서 사라지게 됩니다. 위 상태에서 이어서 [5번] 위치에서 인형을 집어 바구니에 쌓으면 같은 모양 인형 두 개가 없어집니다.

크레인 작동 시 인형이 집어지지 않는 경우는 없으나 만약 인형이 없는 곳에서 크레인을 작동시키는 경우에는 아무런 일도 일어나지 않습니다. 또한 바구니는 모든 인형이 들어갈 수 있을 만큼 충분히 크다고 가정합니다. (그림에서는 화면표시 제약으로 5칸만으로 표현하였음)

게임 화면의 격자의 상태가 담긴 2차원 배열 board와 인형을 집기 위해 크레인을 작동시킨 위치가 담긴 배열 moves가 매개변수로 주어질 때, 크레인을 모두 작동시킨 후 터트려져 사라진 인형의 개수를 return 하도록 solution 함수를 완성해주세요.

[제한사항]
  • board 배열은 2차원 배열로 크기는 "5 x 5" 이상 "30 x 30" 이하입니다.
  • board의 각 칸에는 0 이상 100 이하인 정수가 담겨있습니다.
    • 0은 빈 칸을 나타냅니다.
    • 1 ~ 100의 각 숫자는 각기 다른 인형의 모양을 의미하며 같은 숫자는 같은 모양의 인형을 나타냅니다.
  • moves 배열의 크기는 1 이상 1,000 이하입니다.
  • moves 배열 각 원소들의 값은 1 이상이며 board 배열의 가로 크기 이하인 자연수입니다.
입출력 예boardmovesresult
[[0,0,0,0,0],[0,0,1,0,3],[0,2,5,0,1],[4,2,4,4,2],[3,5,1,3,1]] [1,5,3,5,1,2,1,4] 4
입출력 예에 대한 설명

입출력 예 #1

인형의 처음 상태는 문제에 주어진 예시와 같습니다. 크레인이 [1, 5, 3, 5, 1, 2, 1, 4] 번 위치에서 차례대로 인형을 집어서 바구니에 옮겨 담은 후, 상태는 아래 그림과 같으며 바구니에 담는 과정에서 터트려져 사라진 인형은 4개 입니다.

 

나의 풀이

  • moves 배열을 forEach 문으로 순회한다.
  • 움직이고자 하는 줄의 가장 마지막이 0이면 더 이상 꺼낼 것이 없고, 로직을 실행하지 않아도되므로 if문으로 조건을 걸어준다.
  • board 배열을 for문으로 순회한다. (break 조건을 사용하기 위해 for문 사용)
  • 아래칸으로 넘어가면서 해당 값이 0이 아닐 경우에만 실행되기 위해 if문으로 조건을 걸어준다.
  • 스택의 가장 위칸의 값과 넣고자 하는 값이 같다면 pop해준 뒤 answer에 2를 더해준다.
  • 마지막 값과 같지 않다면 해당 값을 스택에 push 해준다.
  • 처리 후 0으로 값을 바꿔준다. 
function solution(board, moves) {
  var answer = 0;
  let basket = [];

  moves.forEach((move) => {
    if (board[board.length - 1][move - 1])
      for (let i = 0; i <= board.length; i++) {
        if (board[i][move - 1]) {
          if (
            basket.length > 0 &&
            basket[basket.length - 1] === board[i][move - 1]
          ) {
            basket.pop();
            answer += 2;
          } else {
            basket.push(board[i][move - 1]);
          }
          board[i][move - 1] = 0;
          break;
        }
      }
  });
  return answer;
}

 

다른 사람의 풀이

  • 행과 열을 반전시켜주는 transpose 함수를 정의하여 board를 넘겨준 후,
  • 0이 아닌 값들만 걸러내어 행의 순서를 뒤집는다. (이렇게 하면 전체를 순회하지 않고 가장 마지막값만 참조할 수 있다.)
  • moves를 for문으로 순회한다.
  • stacks에서 move하고자 하는 행의 가장 마지막 값을 pop한 값이 존재하면 basket에서 가장 마지막 값과 같은지 비교한다.
  • 같으면 basket에서 pop을 하고 resulte에 2를 더해준다. 
  • 다르면 basket에 해당 값을 push한다. 
const transpose = (matrix) =>
  matrix.reduce(
    (result, row) => row.map((_, i) => [...(result[i] || []), row[i]]),
    []
  );
/* 
위 transpose 함수에 matrix가 [
      [0, 0, 0, 0, 0],
      [0, 0, 1, 0, 3],
      [0, 2, 5, 0, 1],
      [4, 2, 4, 4, 2],
      [3, 5, 1, 3, 1],
    ]으로 주어질 경우, 
    결과 값은 
    [
      [0, 0, 0, 4, 3],
      [0, 0, 2, 2, 5],
      [0, 1, 5, 4, 1],
      [0, 0, 0, 4, 3],
      [0, 3, 1, 2, 1],
    ]이다. 
*/
const solution = (board, moves) => {
  const stacks = transpose(board).map((row) =>
    row.reverse().filter((el) => el !== 0)
  );

  /*
    위 코드 실행 후 stacks의 결괏값은
    [
      [3, 4],
      [5, 2, 2],
      [1, 4, 5, 1],
      [3, 4],
      [1, 2, 1, 3],
    ] 이다.
  */
  const basket = [];
  let result = 0;

  for (const move of moves) {
    const pop = stacks[move - 1].pop();
    if (!pop) continue;
    if (pop === basket[basket.length - 1]) {
      basket.pop();
      result += 2;
      continue;
    }
    basket.push(pop);
  }

  return result;
};

 

 

  matrix.reduce(
    (result, row) => row.map((_, i) => [...(result[i] || []), row[i]]),
    []
  );

특히나 위 부분은 헷갈리지만 이해하고 나니 신기하고 인상깊다. 잘 기억해두어야겠다. 

나의 풀이와 다른 사람의 풀이 결과 비교

나의 코드에 비해서 데이터의 갯수가 많아질수록 효율이 좋아지는 것을 확인할 수 있다.

 


2020 카카오 인턴십 | 키패드 누르기 #67256

더보기

문제 설명

스마트폰 전화 키패드의 각 칸에 다음과 같이 숫자들이 적혀 있습니다.

이 전화 키패드에서 왼손과 오른손의 엄지손가락만을 이용해서 숫자만을 입력하려고 합니다.
맨 처음 왼손 엄지손가락은 * 키패드에 오른손 엄지손가락은 # 키패드 위치에서 시작하며, 엄지손가락을 사용하는 규칙은 다음과 같습니다.

  1. 엄지손가락은 상하좌우 4가지 방향으로만 이동할 수 있으며 키패드 이동 한 칸은 거리로 1에 해당합니다.
  2. 왼쪽 열의 3개의 숫자 1, 4, 7을 입력할 때는 왼손 엄지손가락을 사용합니다.
  3. 오른쪽 열의 3개의 숫자 3, 6, 9를 입력할 때는 오른손 엄지손가락을 사용합니다.
  4. 가운데 열의 4개의 숫자 2, 5, 8, 0을 입력할 때는 두 엄지손가락의 현재 키패드의 위치에서 더 가까운 엄지손가락을 사용합니다.
    4-1. 만약 두 엄지손가락의 거리가 같다면, 오른손잡이는 오른손 엄지손가락, 왼손잡이는 왼손 엄지손가락을 사용합니다.

순서대로 누를 번호가 담긴 배열 numbers, 왼손잡이인지 오른손잡이인 지를 나타내는 문자열 hand가 매개변수로 주어질 때, 각 번호를 누른 엄지손가락이 왼손인 지 오른손인 지를 나타내는 연속된 문자열 형태로 return 하도록 solution 함수를 완성해주세요.

[제한사항]
  • numbers 배열의 크기는 1 이상 1,000 이하입니다.
  • numbers 배열 원소의 값은 0 이상 9 이하인 정수입니다.
  • hand는 "left" 또는 "right" 입니다.
    • "left"는 왼손잡이, "right"는 오른손잡이를 의미합니다.
  • 왼손 엄지손가락을 사용한 경우는 L, 오른손 엄지손가락을 사용한 경우는 R을 순서대로 이어붙여 문자열 형태로 return 해주세요.

입출력 예numbershandresult
[1, 3, 4, 5, 8, 2, 1, 4, 5, 9, 5] "right" "LRLLLRLLRRL"
[7, 0, 8, 2, 8, 3, 1, 5, 7, 6, 2] "left" "LRLLRRLLLRR"
[1, 2, 3, 4, 5, 6, 7, 8, 9, 0] "right" "LLRLLRLLRL"
입출력 예에 대한 설명

입출력 예 #1

순서대로 눌러야 할 번호가 [1, 3, 4, 5, 8, 2, 1, 4, 5, 9, 5]이고, 오른손잡이입니다.

왼손 위치오른손 위치눌러야 할 숫자사용한 손설명
* # 1 L 1은 왼손으로 누릅니다.
1 # 3 R 3은 오른손으로 누릅니다.
1 3 4 L 4는 왼손으로 누릅니다.
4 3 5 L 왼손 거리는 1, 오른손 거리는 2이므로 왼손으로 5를 누릅니다.
5 3 8 L 왼손 거리는 1, 오른손 거리는 3이므로 왼손으로 8을 누릅니다.
8 3 2 R 왼손 거리는 2, 오른손 거리는 1이므로 오른손으로 2를 누릅니다.
8 2 1 L 1은 왼손으로 누릅니다.
1 2 4 L 4는 왼손으로 누릅니다.
4 2 5 R 왼손 거리와 오른손 거리가 1로 같으므로, 오른손으로 5를 누릅니다.
4 5 9 R 9는 오른손으로 누릅니다.
4 9 5 L 왼손 거리는 1, 오른손 거리는 2이므로 왼손으로 5를 누릅니다.
5 9 - -  

따라서 "LRLLLRLLRRL"를 return 합니다.

입출력 예 #2

왼손잡이가 [7, 0, 8, 2, 8, 3, 1, 5, 7, 6, 2]를 순서대로 누르면 사용한 손은 "LRLLRRLLLRR"이 됩니다.

입출력 예 #3

오른손잡이가 [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]를 순서대로 누르면 사용한 손은 "LLRLLRLLRL"이 됩니다.

나의 풀이

수학에서 두 점 사이의 거리를 구하는 공식을 활용했고,

원래는 제곱 후 더한 다음 루트를 씌워주는 것이 올바른 거리 값이지만 여기에서는 비교만 하면 되므로 절댓값으로 바꾼 후 더한 값을 비교하였다.

1,4,7  / 3,6,9 를 비교하는 if문에서 정규표현식을 사용하는 다른 사람의 풀이도 봤는데 시간이 더 오래걸려서 그대로 두었다. 

function solution(numbers, hand) {
  let answer = "";
  hand = hand === "right" ? "R" : "L";

  const key = {
    1: [0, 0],
    2: [0, 1],
    3: [0, 2],
    4: [1, 0],
    5: [1, 1],
    6: [1, 2],
    7: [2, 0],
    8: [2, 1],
    9: [2, 2],
    "*": [3, 0],
    0: [3, 1],
    "#": [3, 2],
  };

  let left = "*";
  let right = "#";

  const setHand = (num, hand) => {
    if (hand === "R") {
      right = num;
      answer += "R";
    } else if (hand === "L") {
      left = num;
      answer += "L";
    }
  };
  numbers.forEach((num) => {
    if (num === 1 || num === 4 || num === 7) {
      setHand(num, "L");
    } else if (num === 3 || num === 6 || num === 9) {
      setHand(num, "R");
    } else {
      const pos = key[num];
      const leftPos = key[left];
      const rightPos = key[right];
      const disL =
        Math.abs(leftPos[0] - pos[0]) + Math.abs(leftPos[1] - pos[1]);
      const disR =
        Math.abs(rightPos[0] - pos[0]) + Math.abs(rightPos[1] - pos[1]);

      if (disL > disR || (disL === disR && hand === "R")) setHand(num, "R");
      else if (disL < disR || (disL === disR && hand === "L"))
        setHand(num, "L");
    }
  });

  return answer;
}

profile

DEVELOP

@JUNGY00N