코딩테스트/깊이,너비 우선 탐색

퍼즐 조각 채우기

highlev 2025. 2. 27. 18:24

문제 정의

  • 테이블과 게임 보드가 주어진다.
  • 테이블에 놓인 블럭을 상하좌우로 회전하여 게임보드의 빈 공간을 채운다.
  • 주의사항은 다음과 같다.
    • 조각은 한 번에 하나씩 채워 넣음
    • 조각을 반전시킬 수는 없음
    • 게임 보드에 세로 채워 넣은 퍼즐 조각과 인접한 칸이 비어있으면 안 된다. 즉, 한 블럭을 새로운 공간에 끼워넣었을 때, 끼워넣은 그 블럭 상하좌우로 빈 공간이 있으면 안 된다.
  • 예를 들면 다음과 같은 상황은 불가하다.

3번이든 4번이든 한 턴에 한 블럭만 넣을 수 있으므로, 둘 중에 하나만 빈 공간에 채워넣었을 때, 빈공간이 발생함

이런 상황이 올바른 상황이다.

  • 위와 같은 식으로 블럭을 채울 때, 가능한 많은 블럭을 채웠을 때의 블럭 칸수를 리턴

문제 해결 접근

  • 이 문제의 관건은 다음과 같다.
    1. 게임보드의 빈 공간의 위치 및 모양, 테이블의 블럭 위치 및 모양 파악
    2. 테이블의 블럭을 상하좌우로 회전해가면서 게임보드의 특정 빈 공간에 맞는지 확인하기

게임 보드와 테이블의 구성 파악하기

  • 게임보드와 테이블의 구성을 파악하기 위해서, bfs 알고리즘을 사용한다.
  • 게임보드와 테이블의 크기는 똑같기 때문에, 이중 for문을 사용해서 좌표를 일일이 탐색한다.
  • 좌표를 탐색하다가 table[i][j] 가 1이고, visitedTable[i][j]가 false면 bfs를 호출하고, game_table[i][j] 가 0이고, visitedBoard[i][j]가 false일 때도, bfs를 호출한다.
  • 게임보드의 빈 공간 찾기와 테이블의 블럭 찾기 알고리즘은 동일하기 때문에, 동일한 메서드를 사용하지만, 메서드의 파라미터로 이 둘을 구분하여 알고리즘의 세부 디테일을 조정한다.
  • bfs 메서드의 파라미터로는
    • 이중 for문을 돌면서 발견한 특정 좌표의 x값
    • y값
    • 게임보드나 테이블의 방문처리 배열
    • 게임보드나 테이블 판
    • 게임보드와 테이블의 구분자
    • 빈 공간 정보들 or 블럭 정보들이 담긴 list
  • bfs를 이용해서 도형의 모양을 알 수 있는 방법은, 바로 좌표를 이용하는 것이다. 
  • bfs가 호출될 때마다, int[] 형 리스트 tempList를 만들어서, 그 리스트에다 현재 발견된 빈 공간이나 블럭을 구성하는 좌표 정보들을 모두 저장한다.
  • 게임보드나 테이블 둘다 인접한 빈 공간 or 블럭이 없기 때문에, bfs를 이용해서 상하좌우로 이동해서 탐색을 하다보면, 결국에는 tempList에는 현재 도형에 대한 모든 좌표들이 저장되게 된다.
  • tempList에 좌표를 저장할 때는, 상대 좌표로 저장해야 나중에 다른 도형과의 비교가 수월하기 때문에, 도형의 제일 머리 부분의 좌표를 {0,0}으로 고정하고 좌표를 구성한다.
  • 이렇게 tempList가 전부 구성되면, 이 tempList를, bfs 메서드의 마지막 파라미터의 list에 추가한다.

테이블의 블럭을 상하좌우로 회전해가면서 게임보드의 특정 빈 공간에 맞는지 확인하기

  • 테이블의 블럭을 회전할 때는, 정렬이 필요하다.
  • 왜냐하면 빈 공간과 블럭이 딱 들어맞는지를 확인하기 위해서는, 이 둘에 대한 순차 탐색을 이용해서, 모든 좌표가 서로 일치할 때, 빈 공간에 블럭이 들어맞는다는 것을 확인할 수 있다.
  • 순차탐색을 하기 위해서는, 게임보드나 테이블이나 똑같은 기준에 의해 정렬되어 있어야 서로 비교를 할 수 있기 때문에 둘 다 정렬을 하는 것이다.
  • 정렬만 하면 되는 것은 아니다. 만약에 테이블의 블럭을 회전하게 되면, 원래의 원점은 원점이 아니게 되기 때문에, 새로 원점을 지정해주는 과정이 필요하다.
  • 이렇게 90도씩 회전을 하고, 모든 좌표를 서로 비교하는 과정에서, 만약 모든 좌표가 서로 일치한다면 true를  리턴하고, 하나라도 일치하지 않는다면 바로 다음 회전을 진행하는 식으로 블록이 특정 빈 공간과 맞는지 확인한다.
  • 특정 빈 공간과 블럭이 들어맞는다면, 더 이상 블럭은 사용이 불가하기 때문에, 방문처리를 하고, 다른 블럭들은 이제 그 공간에 끼울 수 없기 때문에 break문을 통해 탈출하여 다음 빈 공간을 탐색한다.

코드

import java.util.*;
class Solution {
    int[][] dir = {{0,1},{1,0},{-1,0},{0,-1}};
    public int solution(int[][] game_board, int[][] table) {
        List<List<int[]>> empties = new ArrayList<>();
        List<List<int[]>> blocks = new ArrayList<>();
        boolean[][] visitedBoard = new boolean[game_board.length][game_board.length];
        boolean[][] visitedTable = new boolean[table.length][table.length];
        for(int i = 0 ;i<game_board.length;i++){
            for(int j = 0;j<game_board.length;j++){
                if(game_board[i][j]==0&&!visitedBoard[i][j]){
                    visitedBoard[i][j] = true;
                    bfs(0,i,j,game_board,visitedBoard,empties);
                }
                if(table[i][j]==1&&!visitedTable[i][j]){
                    visitedTable[i][j] = true;
                    bfs(1,i,j,table,visitedTable,blocks);
                }
            }
        }
        return fillBoard(blocks,empties);
    }
    public int fillBoard(List<List<int[]>>blocks, List<List<int[]>>empties){
        int size = 0;
        boolean[] visited = new boolean[blocks.size()];
        for(int i = 0; i<empties.size();i++){
            for(int j = 0;j<blocks.size();j++){
                if(empties.get(i).size()==blocks.get(j).size()&&!visited[j]){
                    if(isRotate(empties.get(i),blocks.get(j))){
                        size += empties.get(i).size();
                        visited[j] = true;
                        break;
                    }
                }
            }
        }
        return size;
    }
    public void bfs(int check, int startX, int startY, int[][] map, boolean[][] visited, List<List<int[]>> list){
        Queue<int[]> queue = new LinkedList<>();
        List<int[]> tempList = new ArrayList<>();
        queue.add(new int[]{startX,startY});
        tempList.add(new int[]{0,0});
        while(!queue.isEmpty()){
            int[] curPos = queue.poll();
            for(int i = 0; i<4;i++){
                int nextX = curPos[0] + dir[i][0];
                int nextY = curPos[1] + dir[i][1];
                if(nextX<0||nextY<0||nextX>=map.length||nextY>=map.length)
                    continue;
                if(map[nextX][nextY]==check&&!visited[nextX][nextY]){
                    tempList.add(new int[]{nextX-startX, nextY-startY});
                    queue.add(new int[]{nextX, nextY});
                    visited[nextX][nextY] = true;
                }
                
            }
        }
        list.add(tempList);
    }
    boolean isRotate(List<int[]> table, List<int[]>board){
        board.sort((o1,o2)->{
            if(o1[0]==o2[0])
                return o1[1] - o2[1];
            return o1[0] - o2[0];
        });
        for(int i = 0;i<4;i++){
            table.sort((o1,o2)->{
                if(o1[0]==o2[0])
                    return o1[1] - o2[1];
                return o1[0] - o2[0];
            });
            int centerX = table.get(0)[0];
            int centerY = table.get(0)[1];
            for(int j = 0;j<table.size();j++){
                table.get(j)[0] -= centerX;
                table.get(j)[1] -= centerY;
            }
            boolean isFit = true;
            for(int j = 0;j<table.size();j++){
                int[] curBoardPoint = board.get(j);
                int[] curTablePoint = table.get(j);
                if(curBoardPoint[0]!=curTablePoint[0]||curBoardPoint[1]!=curTablePoint[1]){
                    isFit = false;
                    break;
                }
            }
            if(isFit)
                return true;
            for(int j = 0;j<table.size();j++){
                int temp = table.get(j)[0];
                table.get(j)[0] = table.get(j)[1];
                table.get(j)[1] = -temp;
            }
        }
        return false;
    }
                    
}

느낀점

내가 지금까지 풀어본 코테 문제중에 제일 어려웠다. 처음부터 끝까지 다른 블로그를 찾아가면서 코드를 작성했다. 블럭을 상하좌우로 회전하는 로직도 처음 짜보았고, 좌표 리스트를 bfs를 이용해서 형성한 다음에, 그 좌표 리스트를 관리하는 리스트를 구현하는 것도 이번이 처음이었다. 이런 문제는 어렵긴 하지만 특징이 뚜렷하기 때문에, 이와 비슷한 다른 문제가 나오면 이번의 경험을 통해서 쉽게 구현할 수 있을 것 같다. bfs로 빈 공간과 블럭의 모양을 찾아내고, 그걸 이용해서 최종 정답을 도출해내는 유형은 이번이 처음이어서 더 어렵게 느껴졌던 것 같다. 매번 쉬운 문제만 풀어간다면 성장하지 못하기 때문에, 이번 기회를 통해서 한 단계 더 성장한 것 같아서 뿌듯하다.

'코딩테스트 > 깊이,너비 우선 탐색' 카테고리의 다른 글

출퇴근길  (0) 2026.07.20
아이템 줍기  (0) 2025.02.19
단어 변환  (0) 2025.02.19
게임 맵 최단거리  (0) 2025.02.15
네트워크  (0) 2025.02.13