[프로그래머스] 퍼즐 조각 채우기(위클리 챌린지 3주차)

PROBLEM

https://programmers.co.kr/learn/courses/30/lessons/84021

SOLUTION

모든 경우의 수를 해보는 완전탐색의 방식으로 생각했다. table로부터 블록을 판별하고, game_board에 블록을 한 칸, 한 칸 넣어보면서 들어가도 되는지 아닌지 판단하고, 가능하면 넣는다.

말로는 쉽지만, 구현하려니 꽤나 복잡한 과정을 거친다.

1. 블록에 번호 붙이기

table에서 0은 빈칸, 1조각이 놓인 칸을 의미하므로, 블록에 번호는 2번부터 시작한다. table을 순회하면서, 1을 만나면 BFS를 진행하며 해당 영역에 번호를 붙여준다.

블록에 번호 붙이기

Block 클래스

Block 클래스는 해당 블록의 번호(index)와 각 위치들(positions)을 담는 클래스이다. index의 필요성은 이후에 설명하도록 하고, positions는 블록의 좌표들을 저장하는 리스트이다. table을 순회하면서 블록들을 저장하는데, 블록의 한 점을 (0, 0)이라고 하고 그에 대한 상대적인 좌표들을 저장한다.

Block 클래스

2. table을 회전시키면서 블록 리스트 만들기

각 블록은 회전이 가능하기 때문에, table을 회전시키면서 사용가능한 블록의 모양들을 블록 리스트(blocks)에 저장한다.

Block 클래스

3. game_board에 블록들을 대보면서 적절하면 블록 삽입

game_board를 순회하면서 각 블록을 대보면서 적절한지 판단한다. 사용된적 없는 블록들을 game_board에 하나씩 대보는데, 각 모든 칸이 비워져 있아야 하고, 삽입했을 때, 인접한 칸들 중 빈 칸이 없어야 하는 조건이 있다. 조건을 충족한다면 해당 빈 공간에 블록을 삽입하면 된다.

Block 클래스

여기서 블록에 번호를 붙였던(1번 과정) 이유가 나오는데, 예를 들어, 블록 리스트에 그냥 5번 블록, 90도 회전한 5번 블록, 180도 회전한 5번 블록, 270도 회전한 5번 블록이 들어간다.(2번 과정 이미지)

이후에 블록을 game_board에 대보면서 넣을 수 있으면 넣는데, 270도 회전한 5번 블록이 딱 맞아서 넣는다고 해보자. 그렇다면 이후에 다른 5번 블록을 또 다시 넣지 않기 위해 5번 블록 사용했음을 체크해야 하므로 각 블록의 번호가 필요하게 된다.

Block 클래스

블록을 삽입할 때에는 3가지 조건을 만족해야한다.

  1. 블록을 채울 모든 칸은 빈칸이어야한다.
  2. 블록을 채운 후에 인접한 칸에 빈칸이 없어야한다.
  3. 이전에 사용한 적 없는 블록이어야 한다.

해당 조건을 만족하면 블록을 채우고, 채울 때마다 채워진 칸의 개수를 answer에 누적하여 답을 구한다.

SOURCE CODE (JAVA)

긴 구현과정이 될 것 같아, 최대한 클린하게 코드를 구현하도록 노력했다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
import java.util.*;

public class Solution {
  class Pos {
    int r, c;
    Pos(int r, int c) {
      this.r = r;
      this.c = c;
    }
  }

  class Block {
    int index;
    List<Pos> positions;

    Block(int index, List<Pos> positions) {
      this.index = index;
      this.positions = positions;
    }
  }

  private List<Block> blocks = new ArrayList<>();
  private int maxIndexOfBlock = 0;
  private boolean[] used;
  private int answer = 0;

  public int solution(int[][] game_board, int[][] table) {
    indexingBlocks(table);
    for (int i = 0; i < 4; i++) {
      table = rotate(table);
      addBlocksInTable(table);
    }

    doGame(game_board);

    return answer;
  }

  private void indexingBlocks(int[][] arr) {
    int index = 2;
    for (int i = 0; i < arr.length; i++) {
      for (int j = 0; j < arr[0].length; j++) {
        if (arr[i][j] == 1) {
          fillIndex(arr, i, j, index);
          index++;
        }
      }
    }
    maxIndexOfBlock = index;
    used = new boolean[maxIndexOfBlock];
  }

  private final int[] dr = {-1, 1, 0, 0};
  private final int[] dc = {0, 0, -1, 1};

  private void fillIndex(int[][] arr, int row, int col, int index) {
    // BFS
    final int R = arr.length;
    final int C = arr[0].length;
    boolean[][] visited = new boolean[R][C];
    Queue<Integer> queue = new LinkedList<>();

    arr[row][col] = index;
    visited[row][col] = true;
    queue.add(row);
    queue.add(col);

    while (!queue.isEmpty()) {
      int r = queue.poll();
      int c = queue.poll();
      for (int d = 0; d < 4; d++) {
        int nr = r + dr[d];
        int nc = c + dc[d];

        if (nr < 0 || nr >= R || nc < 0 || nc >= C ||
            visited[nr][nc] ||
            arr[nr][nc] == 0) {
          continue;
        }

        arr[nr][nc] = index;
        visited[nr][nc] = true;
        queue.add(nr);
        queue.add(nc);
      }
    }
  }

  private int[][] rotate(int[][] arr) {
    final int R = arr.length;
    final int C = arr[0].length;

    int[][] result = new int[R][C];
    for (int i = 0; i < R; i++) {
      for (int j = 0; j < C; j++) {
        result[i][j] = arr[C - j - 1][i];
      }
    }

    return result;
  }

  private void addBlocksInTable(int[][] table) {
    final int R = table.length;
    final int C = table[0].length;

    boolean[] visited = new boolean[maxIndexOfBlock];
    for (int i = 0; i < R; i++) {
      for (int j = 0; j < C; j++) {
        if (table[i][j] > 1 && !visited[table[i][j]]) {
          int idx = table[i][j];
          visited[idx] = true;
          List<Pos> positions = getPositions(table, idx);
          blocks.add(new Block(idx, positions));
        }
      }
    }
  }

  private List<Pos> getPositions(int[][] arr, int index) {
    List<Pos> result = new ArrayList<>();
    final int R = arr.length;
    final int C = arr[0].length;

    Pos start = null;
    for (int i = 0; i < R; i++) {
      for (int j = 0; j < C; j++) {
        if (arr[i][j] == index) {
          if (start == null) {
            start = new Pos(i, j);
            result.add(new Pos(0, 0));
          } else {
            result.add(new Pos(i - start.r, j - start.c));
          }
        }
      }
    }

    return result;
  }

  private void doGame(int[][] gameBoard) {
    final int R = gameBoard.length;
    final int C = gameBoard[0].length;

    for (int i = 0; i < R; i++) {
      for (int j = 0; j < C; j++) {
        tryBlockAtPoint(gameBoard, i, j);
      }
    }
  }

  private void tryBlockAtPoint(int[][] gameBoard, int row, int col) {
    for (Block block : blocks) {
      if (!used[block.index] && isFitBlock(gameBoard, row, col, block)) {
        used[block.index] = true;
        int result = insertBlock(gameBoard, row, col, block);
        answer += result;
      }
    }
  }

  private boolean isFitBlock(int[][] arr, int row, int col, Block block) {
    boolean result = false;

    if (checkAllEmpty(arr, row, col, block)) {
      insertBlock(arr, row, col, block);
      result = checkNoEmptyAround(arr, row, col, block);
      deleteBlock(arr, row, col, block);
    }

    return result;
  }

  private int insertBlock(int[][] arr, int row, int col, Block block) {
    for (Pos pos : block.positions) {
      int nr = row + pos.r;
      int nc = col + pos.c;
      arr[nr][nc] = block.index;
    }

    return block.positions.size();
  }

  private boolean checkNoEmptyAround(int[][] arr, int row, int col, Block block) {
    final int R = arr.length;
    final int C = arr[0].length;

    for (Pos pos : block.positions) {
      for (int d = 0; d < 4; d++) {
        int nr = row + pos.r + dr[d];
        int nc = col + pos.c + dc[d];
        if (nr < 0 || nr >= R || nc < 0 || nc >= C) {
          continue;
        }
        if (arr[nr][nc] == 0) {
          return false;
        }
      }
    }

    return true;
  }

  private void deleteBlock(int[][] arr, int row, int col, Block block) {
    for (Pos pos : block.positions) {
      int nr = row + pos.r;
      int nc = col + pos.c;
      arr[nr][nc] = 0;
    }
  }

  private boolean checkAllEmpty(int[][] arr, int row, int col, Block block) {
    final int R = arr.length;
    final int C = arr[0].length;

    for (Pos pos : block.positions) {
      int nr = row + pos.r;
      int nc = col + pos.c;
      if (nr < 0 || nr >= R || nc < 0 || nc >= C) {
        return false;
      }
      if (arr[nr][nc] != 0) {
        return false;
      }
    }

    return true;
  }
}

OFF THE RECORD

프로그래머스 위클리 챌린지 문제들을 풀어보다가, 이게 3주차 문제인데 1, 2주차 문제에 비해 급상승한 난이도를 가지고 있다. ‘이거 어디서 본 문제 같은데?’ 라는 셍긱이 스쳐지나갔는데, 어디 기업 코딩테스트에서 봤었던 문제인 듯 했다.

푸는데 아마 2시간이 안되게 걸린거 같다. 어느정도 로직은 짜여졌지만, 이렇게 푸는게 맞는건가 싶을정도로 코드가 길어지고, 복잡해질수록 디버깅이 힘들어졌다. 때문에 최대한 추상화 수준을 나누어서 코딩했다.

처음에는 블록을 넣을 수 있는 상황에서 블록을 넣는 경우와 넣지않고 넘어가는 경우로 나누어지지 않을까 라는 생각이 있어서 재귀적으로 짜야한다고 생각했는데, 그럴 필요없이 무조건 넣을 수 있으면 블록을 넣으면 됐었다.

위에 열심히 해결책을 설명해봤지만 그래도 복잡하긴 했다. 최대한 코드도 정성스레 짰으니 코드로 이해하는 것이 내 의도를 정확하게 이해하는 것이리라.

구현하면서 사용했던 테스트케이스를 공유한다.
int[][] game_board = {
    {1, 1, 0, 0, 1, 0},
    {0, 0, 1, 0, 1, 0},
    {0, 1, 1, 0, 0, 1},
    {1, 1, 0, 1, 1, 1},
    {1, 0, 0, 0, 1, 0},
    {0, 1, 1, 1, 0, 0}
};
int[][] table = {
    {1, 0, 0, 1, 1, 0},
    {1, 0, 1, 0, 1, 0},
    {0, 1, 1, 0, 1, 1},
    {0, 0, 1, 0, 0, 0},
    {1, 1, 0, 1, 1, 0},
    {0, 1, 0, 0, 0, 0}
};
// return 14

int[][] game_board = {
    {0, 0, 0},
    {1, 1, 0},
    {1, 1, 1}
};
int[][] table = {
    {1, 1, 1},
    {1, 0, 0},
    {0, 0, 0}
};
// return 0

int[][] game_board = {
    {0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0},
    {1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 1, 1, 1},
    {0, 1, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 0},
    {0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 0},
    {0, 1, 1, 1, 0, 0, 1, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 1},
    {1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0},
    {0, 0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 1, 1, 1, 1, 0, 0, 1},
    {1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 1, 0},
    {0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 1, 1, 0, 0, 0},
    {1, 1, 0, 1, 1, 0, 1, 1, 1, 1, 0, 1, 0, 0, 0, 1, 1, 1},
    {0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 1, 0, 1, 0},
    {1, 1, 1, 1, 0, 1, 1, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1, 0},
    {0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0},
    {1, 0, 1, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 0, 1},
    {1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0},
    {0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 1, 0, 0, 0, 0, 0, 1, 0},
    {0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 1, 1, 1, 1, 1, 1, 0},
    {0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 0, 0, 0, 0, 0, 1, 0}
};
int[][] table = {
    {1, 1, 1, 1, 1, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0},
    {0, 0, 0, 0, 0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1, 1, 0},
    {1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 1},
    {1, 1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1},
    {1, 1, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 1, 0, 0, 1, 1},
    {0, 0, 0, 1, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0},
    {1, 1, 1, 0, 0, 0, 0, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1, 1},
    {0, 0, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, 0},
    {1, 0, 1, 0, 0, 0, 0, 1, 0, 1, 1, 0, 1, 1, 0, 1, 1, 1},
    {1, 0, 1, 0, 1, 1, 1, 1, 0, 1, 1, 0, 0, 0, 1, 1, 1, 0},
    {1, 1, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 1, 0, 0, 0, 0},
    {0, 0, 1, 1, 1, 1, 0, 1, 1, 0, 1, 0, 0, 0, 1, 1, 0, 1},
    {1, 1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1},
    {1, 1, 0, 0, 1, 1, 1, 0, 1, 1, 0, 1, 0, 1, 0, 1, 0, 1},
    {0, 0, 1, 1, 0, 1, 1, 0, 1, 0, 1, 1, 0, 0, 0, 1, 0, 0},
    {1, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 0, 1, 0, 1},
    {0, 0, 0, 0, 1, 0, 1, 1, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1},
    {0, 1, 1, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1}
};
// return 73

int[][] game_board = {
    {0, 0, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0},
    {1, 1, 1, 1, 1, 1, 0, 0, 0, 1, 0, 0},
    {0, 0, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0},
    {1, 0, 1, 1, 1, 0, 1, 0, 1, 0, 0, 1},
    {0, 1, 0, 0, 0, 0, 1, 0, 1, 0, 0, 0},
    {0, 0, 1, 1, 1, 0, 1, 0, 1, 1, 0, 1},
    {0, 1, 0, 0, 0, 1, 1, 0, 0, 0, 1, 0},
    {0, 0, 1, 0, 1, 0, 0, 1, 1, 1, 0, 0},
    {1, 1, 0, 0, 1, 0, 0, 1, 1, 1, 1, 0},
    {0, 0, 1, 1, 0, 1, 0, 1, 1, 1, 0, 0},
    {0, 0, 1, 0, 0, 1, 0, 1, 1, 0, 1, 1},
    {0, 0, 1, 0, 0, 0, 1, 0, 0, 0, 0, 0}
};
int[][] table = {
    {1, 1, 1, 0, 1, 1, 1, 0, 0, 0, 1, 1},
    {1, 1, 0, 0, 0, 0, 1, 1, 1, 0, 1, 1},
    {1, 0, 1, 0, 1, 0, 0, 0, 0, 1, 1, 0},
    {0, 0, 1, 1, 1, 0, 0, 1, 1, 0, 0, 0},
    {1, 1, 0, 1, 0, 0, 0, 1, 1, 1, 0, 0},
    {1, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0},
    {1, 0, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1},
    {1, 1, 0, 1, 0, 1, 1, 1, 0, 0, 0, 1},
    {0, 0, 0, 1, 1, 0, 0, 0, 1, 1, 0, 1},
    {1, 1, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1},
    {1, 1, 1, 0, 0, 0, 1, 0, 1, 1, 0, 1},
    {1, 0, 0, 1, 1, 1, 1, 0, 0, 1, 0, 1}
};
// return 54

댓글 남기기