• TOC {:toc}

이 글은 프로그래머스의 84021번 문제를 자바스크립트(JavaScript)으로 풀이한 것을 모아놓은 글입니다.

일종의 연습 기록이며 제가 정답을 받은 코드와 참고할만한 다른 코드를 같이 기록합니다. 필요한 경우 코드에 대한 해설을 기록합니다만 코드는 통과했어도 해설은 틀릴 수 있기 때문에 가볍게 참고해주시길 부탁드립니다. 피드백은 편하신 방법으로 자유롭게 주시면 감사하겠습니다.

2024.01.12

테스트 통과 시간 메모리
테스트 1 통과 3.65ms 34.5MB
테스트 2 통과 3.64ms 34.5MB
테스트 3 통과 3.27ms 34.5MB
테스트 4 통과 5.80ms 34.4MB
테스트 5 통과 2.80ms 34.4MB
테스트 6 통과 33.31ms 43.4MB
테스트 7 통과 28.84ms 43.1MB
테스트 8 통과 53.96ms 42.5MB
테스트 9 통과 25.85ms 39.9MB
테스트 10 통과 130.36ms 48.5MB
테스트 11 통과 335.62ms 47.1MB
테스트 12 통과 145.50ms 46.3MB
테스트 13 통과 193.64ms 46.2MB
테스트 14 통과 4.88ms 34.8MB
테스트 15 통과 1.19ms 33.6MB
테스트 16 통과 1.77ms 33.6MB
테스트 17 통과 1.34ms 33.7MB
테스트 18 통과 1.33ms 33.7MB
테스트 19 통과 1.09ms 33.5MB
테스트 20 통과 0.94ms 33.6MB
테스트 21 통과 0.72ms 33.5MB
테스트 22 통과 1.10ms 33.4MB
단계 시작 시각 끝난 시각 걸린 시간
문제 이해 10:48:38 10:51:35
풀이 생각 10:52:44 11:00:15
코딩 1 11:01:13 11:31:02
코딩 2 11:41:20 12:32:19
코딩 3 13:20:47 13:59:35
코딩 4 21:14:33 22:11:58
// DFS로 연결된 칸(빈 공간 혹은 블록)의 좌표를 구해 반환하는 함수
function getShape(startX, startY, base, target) {
    const stack = [[startX, startY]];
    const shape = [[startX, startY]];
    const delta = [
        [1, 0],
        [0, 1],
        [-1, 0],
        [0, -1],
    ];

    while (stack.length) {
        const [x, y] = stack.pop();
        delta.forEach(([dx, dy]) => {
            if (base[y + dy]?.[x + dx] === target) {
                const [nx, ny] = [x + dx, y + dy];
                base[ny][nx] = Number(!target);
                stack.push([nx, ny]);
                shape.push([nx, ny]);
            }
        });
    }
    return shape;
}

// 모양 비교를 위해 해당 모양을 감싸는 박스를 생성하는 함수
// 왼쪽 위를 (0, 0)으로 1이 모양에 해당하는 부분, 0이 해당하지 않는 부분
function createBox(shape) {
    const [minX, minY, maxX, maxY] = shape.reduce(
        (acc, curr) => [
            Math.min(acc[0], curr[0]),
            Math.min(acc[1], curr[1]),
            Math.max(acc[2], curr[0]),
            Math.max(acc[3], curr[1]),
        ],
        [...shape[0], ...shape[0]]
    );
    const normalized = shape.map(([x, y]) => [x - minX, y - minY]);
    const box = Array.from(new Array(maxY - minY + 1), () =>
        new Array(maxX - minX + 1).fill(0)
    );
    normalized.forEach(([x, y]) => (box[y][x] = 1));

    return box;
}

// 블록을 90도 회전시키는 함수
function rotate90(shape) {
    return shape.map(([x, y]) => [-y, x]);
}

// 두 박스가 일치하는지 확인하는 함수
function isEqual(a, b) {
    if (a.length !== b.length || a[0].length !== b[0].length) return false;
    return a.every((row, j) => row.every((col, i) => col === b[j][i]));
}

// 공간에 블록을 끼워넣을 수 있는지 확인하는 함수
// 블록을 90도씩 돌려가면서 piece의 box와 블록의 box가 일치하면 true를 반환
function isFit(a, b) {
    const boxA = createBox(a);

    for (let i = 0; i < 4; i += 1) {
        const boxB = createBox(b);
        if (isEqual(boxA, boxB)) return true;
        b = rotate90(b);
    }
    return false;
}

function solution(game_board, table) {
    const spaces = [];
    const pieces = [];
    for (let y = 0; y < game_board.length; y += 1) {
        for (let x = 0; x < game_board[0].length; x += 1) {
            if (!game_board[y][x]) {
                game_board[y][x] = 1;
                spaces.push(getShape(x, y, game_board, 0));
            }
            if (table[y][x]) {
                table[y][x] = 0;
                pieces.push(getShape(x, y, table, 1));
            }
        }
    }

    // 공간과 블록의 사용 여부를 확인하는 배열
    const spaceUsed = new Array(spaces.length).fill(0);
    const pieceUsed = new Array(pieces.length).fill(0);

    // 사용된 블록의 개수
    let count = 0;
    spaces.forEach((space, i) => {
        pieces.forEach((piece, j) => {
            if (!spaceUsed[i] && !pieceUsed[j] && isFit(space, piece)) {
                count += piece.length;
                spaceUsed[i] = 1;
                pieceUsed[j] = 1;
            }
        });
    });

    return count;
}

아이디어 & 풀이

우선 공간/블록 덩어리의 좌표들을 파악한다.

  • game_boardtable을 순회하면서
  • 각 좌표를 getShape에 전달해 해당 좌표를 포함한 공간/블록 덩어리를 반환 받아 spaces/pieces에 집어 넣는다.
    • DFS로 상하좌우로 나아가며 인접한 공간(0)/블록(1)을 모을 수 있으며
    • 이 과정에서 모인 공간/블록은 다시 사용되지 않도록 반대(공간의 경우 1, 조각의 경우 0)로 채워준다.

공간 덩어리의 집합 spaces와 블록 덩어리의 집합 pieces를 순회하면서 isFit 함수로 각 공간에 블록을 넣을 수 있는지 확인한다.

  • 블록과 공간의 위치한 절대 좌표는 다르므로 이를 비교하기 위해 createBox를 이용해 각각을 좌상단을 (0, 0)으로 하는 직사각 박스로 감싸준다.
    • 타겟인 블록 혹은 공간이 위치한 부분을 1, 아닌 부분을 0으로
  • isEqual 함수를 이용해 두 박스가 같은지 비교한다.
  • 블록은 회전시킬 수 있으므로 박스가 일치하지 않는 경우 rotate90 함수를 이용해 블록을 90도 회전시킨 뒤 다시 박스로 감싸 비교하는 과정을 반복한다.
  • 360도 회전시키는 동안 블록이 일치하면 true를 일치하지 않으면 false를 반환한다.

공간에 블록을 넣을 수 있으면 해당 블록의 크기를 count에 추가한 뒤 모든 공간과 블록을 순회하면 count를 반환한다.

피드백

  • 실행이 너무 느리다. 속도 면에서 개선할 수 있는 부분이나 더 나은 로직이 있는지 확인해봐야할 것 같다.