Bin's Blog

[프로그래머스] Level2 - 프렌즈4블록(Javascript) 본문

Algorithm

[프로그래머스] Level2 - 프렌즈4블록(Javascript)

hotIce 2023. 4. 24. 16:33
728x90
 

프로그래머스

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

programmers.co.kr

▶ 문제 요약

1. 같은 모양의 카카오프렌즈 블록이 2 x 2 형태로 4개가 붙어있을 경우 사라지면서 몇 개의 블록이 사라졌는지 세준다.

2. 같은 블록이 2 x 2에 포함될 수 있으며, 2 x 2 모양이 여러 개 있다면 한꺼번에 지워진다. 

3. 블록이 지워진 후에 있는 블록이 아래로 떨어져 빈 공간을 채우게 된다. 

4. 만약 빈 공간을 채운 후에 다시 2 x 2 형태로 같은 모양의 블록이 모이면 다시 지워지고 떨어지고를 반복하게 된다.

 

▶ 코드

function solution(m, n, board) {
   // 문자열을 분할해서 배열로 변환
   board = board.map(el => el.split("");
   let totalRemoved = 0;
   
   // 더 이상 지울 수 있는 블록이 없을 때까지
   while(true) {
      // 자동으로 중복되는 좌표 제거
      const toRemove = new Set();
      for (let x = 0; x < m - 1; x++) {
          for (let y = 0; y < n - 1; y++) {
              // 3방향 체크해서 맞는 블록인지 확인
              if (board[x][y] &&
                  board[x][y] === board[x][y+1] &&
                  board[x][y] === board[x+1][y] &&
                  board[x][y] === board[x+1][y+1]) {
                  //맞으면 set()에 넣는다
                  toRemove.
                  add(`${x},${y}`).
                  add(`${x},${y+1}`).
                  add(`${x+1},${y}`).
                  add(`${x+1},${y+1}`)
               }
          }
      }
      
      // 더 이상 제거할 블록 없으면 종료
      if (toRemove.size === 0) break;
      // 제거된 개수를 담는다.
      totalRemoved += toRemove.size;
      
      // ","기준으로 나누고 숫자형태로 변환
      toRemove.forEach(coords => {
         const [x, y] = coords.split(",").map(Number);
         // 제거된 블록은 0으로 처리
         board[x][y] = 0;
      }
      
    
      for (let y = 0; y < n; y++) {
          // 세로부터 0이 아닌 값만 나오게 처리
          let newRow = board.
          map(row => row[y]).
          filter(cell => cell !== 0);
          
          // 전체 행의 개수에서 남은 블록의 개수를 빼주고
          // 그리고 이 개수만큼 0으로 채운 배열 생성
          // 이 배열과 남은 블록을 합쳐 완성
          newRow = Array(m - newRow.length)
          .fill(0)
          .concat(newRow);
          
          //이제 가로 인덱스를 기준으로 해서 원래 board에 업데이트
          for (let x = 0; x < m; x++) {
              board[x][y] = newRow[x];
          }
      
      }
      
   }
   
   return totalRemoved;



}

▶ 풀이 과정

1. 입력값이 문자열이기 때문에 split()메서드를 활용해 다 떨어뜨린다. 

 

2. 더 이상 지울 수 없는 블록이 있을 때까지 while 구문을 활용해서 코드를 돌린다.

 

3. set() 함수를 활용해서 중복된 값을 허용하지 않는다. 이 말은 문제에서 2x2 형태의 같은 블록이 여러 군데에 걸쳐 있을 수 있다고 했으므로, 같은 블록이 여러 2x2에서 포함되어 제거될 수 있다.

 

4. 그러면 2중 for문을 돌면서 주어진 입력값 board 안에 2x2쌍이 있는지 확인한다. 3가지 방향만 확인하면 된다. 자신을 기준으로 우측, 아래, 우측아래 방향이다. 좌표로 나타내면 (0, 1), (1, 0), (1, 1)의 방향만 확인하면 된다. 

 

5. 그렇게해서 찾은 블록을 Set()함수add 메서드를 활용해서 넣어준다. 여기서 넣어줄 때 템플릿 리터럴 방식을 사용해서 넣은 이유는 일반적으로 그냥 배열의 형태를 넣으면 동일한 배열을 중복으로 저장할 수 있기 때문에 중복을 방지하기 위해서 사용했다.

 

ex) 예시

// 템플릿 리터럴 사용
const set1 = new Set();
set1.add("0,1");
set1.add("0,1");
console.log(set1.size); // 1

// 배열 사용
const set2 = new Set();
set2.add([0, 1]);
set2.add([0, 1]);
console.log(set2.size); // 2

위처럼  템플릿 리터럴을 사용하면 중복을 제거하기 때문에 중복된 좌표 문제를 해결할 수 있다.

 

6. toRemove.size === 0이라는 말은 더 이상 제거할 블럭이 없다는 의미이며 그렇게 될때 break를 사용해서 while문을 탈출하고 그렇지 않다면 toRemove.size(새롭게 제거할 블록)의 개수를 계속 누적시킨다. 이렇게 처리하면 전체 제거된 블록의 개수를 나타낼 수 있다.

 

7. 이제 2x2크기의 블록을 찾았으면 없애줘야한다. 그러기 위해서 기존의 board에서 해당 블록을 0을 처리해야 한다. forEach() 메서드를 활용해서 toRemove에 담긴 값을 돌면서 '1, 0', => [1, 0] 이렇게 바꿔줘야 한다. 그래서 split(",") , 기준으로 분리해서 map() 함수를 사용해서 바로 숫자형태로 변환해준다.

변환된 값을 destructuring assignment(구조 분해 할당)을 사용해서 두 개의 요소 x, y를 board에서 활용해서 블록에 해당하는 좌표를 0으로 바꿨다.

 

8. 마지막으로 해야하는 부분은 블록을 삭제하면 위에 있던 블록이 아래에 내려오게 하고 그 자리는 0을 채우고 기존 board에 업데이트까지 시켜주는 작업이다. 그림을 먼저 보자.

위의 그림은 입력 1번이다. AAA, AAA 이 6개의 블록이 한 쌍이 되어서 0을 처리한 후의 모습이다. 이제 map()함수와 filter()메서드를 활용해서 세로방향으로 0이 아닌 숫자를 보게 되면 그 바로 밑의 모습처럼 ['c', 'c']의 모습이 된다. 

여기서 헷갈리지 말아야 할 부분은 map함수를 쓰는 부분에서 예를 들어 remove[0]이라면 각 리스트의 0번째 부분을 보는 것이다. 그렇게 해서 remove[4]까지 본다.

그 후에 맨 아래 그림처럼 기존의 공간 빈 공간을 행의 개수 - 빈 공간을 제외한 블록이 차지하는 행의 개수만큼 fill(0)메서드를 활용해서 채우고 그것을 concat(newRow) 메서드를 활용해서 핵심인 빈 공간을 기존의 newRow 앞에 추가한다. 

 

 

 

 

9. 그리고 그 다음에 이중 for문에서 마지막 x는 가로방향이다. 이 부분에서 마지막으로 블록을 삭제하고 그 위에 있던 값을 아래로 내려준 것을 기존의 board에 업데이트 하는 작업이다. board[x][y] = newRow[x]를 하게 되면 [1][0], [2][0], [3][0] 이런식으로 세로 방향으로 보면서 newRow에서 생성했던 배열을 기존 board에 업데이트한다. 마지막으로 누적된 결과값을 리턴하면 된다.

 

 

▶ 배운점

1. 가장 어려웠던 점은 같은 블록이 여러번 포함 될 수 있다는 부분과 블록을 없앤다음에 어떻게 처리해야 하는가에 관한 문제였다.

중복관련한 부분을 처리할 때 Set()함수를 적극적으로 활용하며 빈 공간을 전체 행 블럭 - 남은 블럭 concat()메서드를 활용하면 채울 수 있다는 부분을 배웠다.

 

2. Set()함수에 넣을때 템플릿 리터럴을 활용해야 요소가 중복되는 것을 방지할 수 있다는 것을 기억하자. 행과 열을 조작하는 것을 더 익혀야겠다.

728x90