일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- disjoint set
- 투 포인터
- Java
- MST
- 페르마 소정리
- 분리 집합
- BFS
- BindingAdapter
- SCC
- Meet in the middle
- MySQL
- kruskal
- DP
- Linux
- 알고리즘
- DFS
- 이분 탐색
- spring boot
- concurreny
- 백트래킹
- union-find
- springdoc
- 위상정렬
- 위상 정렬
- 누적 합
- 그래프
- miller-rabin
- tarjan
- kapt
- 구현
- Today
- Total
기맹기 개발 블로그
[BOJ 12100] 2048 (Easy) (Java) 본문
BOJ 12100 2028 (Easy)
난이도 : 골드 2
https://www.acmicpc.net/problem/12100
12100번: 2048 (Easy)
첫째 줄에 보드의 크기 N (1 ≤ N ≤ 20)이 주어진다. 둘째 줄부터 N개의 줄에는 게임판의 초기 상태가 주어진다. 0은 빈 칸을 나타내며, 이외의 값은 모두 블록을 나타낸다. 블록에 쓰여 있는 수는 2
www.acmicpc.net
전략
각 라운드마다 위 / 아래 / 왼쪽 / 오른쪽 4방향으로 이동시킬 수 있다.
문제에서 최대 5번으로 제한을 뒀으므로 모든 경우의 수는 4^5 = 1024이다.
브루트포스로 가능하기 때문에 구현에 초점을 둬서 진행한다.
시행착오
셀 충돌하고 병합하는 과정을 구현하면서 루프의 범위가 잘못된 경우가 있었다.
이 문제에서 많이들 놓치는 조건인 '한 번의 이동에서 이미 합쳐진 블록은 또 합쳐질 수 없다'도 조심해야한다.
탐색을 하기 전에 Board를 이동시켰을 때 결과가 주어진 예시와 모두 일치하도록 요구사항을 먼저 만족시키면서 구현하였다.
코드
// 1st -> 2nd -> 3rd ===merge==> 1st 2nd 3rd
private static int[] merge(int[] prev) {
int n = prev.length;
if (n == 1)
return prev;
int[] merged = new int[n];
System.arraycopy(prev, 0, merged, 0, n);
for (int i = n - 1; i >= 0; i--) {
for (int j = i - 1; j >= 0; j--) {
if (merged[j] == 0) continue;
if (merged[i] == merged[j]) {
merged[i] <<= 1;
merged[j] = 0;
}
break;
}
}
shift(merged, n);
return merged;
}
// 1st <- 2nd <- 3rd ===merge==> 1st 2nd 3rd
private static int[] mergeInverse(int[] prev) {
int n = prev.length;
if (n == 1)
return prev;
int[] merged = new int[n];
System.arraycopy(prev, 0, merged, 0, n);
for (int i = 0; i < n; i++) {
for (int j = i + 1; j < n; j++) {
if (merged[j] == 0) continue;
if (merged[i] == merged[j]) {
merged[i] <<= 1;
merged[j] = 0;
}
break;
}
}
shiftInverse(merged, n);
return merged;
}
한 줄 단위로 이동하도록 구현하였다.
순방향 (moveRight, moveDown) -> merge
역방향 (moveLeft, moveUp) -> mergeInverse
이렇게 따로 구현하였다.
이웃된 칸 (중간에 비어있거나 바로 붙어있는 두 칸)의 수가 같다면 방향에 맞는 위치에 *2를 하고 나머지 칸은 0으로 만든다.
모두 병합이 완료되면 빈칸 없이 밀어 붙이도록 shift를 한다.
private static void shift(int[] merged, int n) {
for (int i = n - 2; i >= 0; i--) {
for (int j = n - 1; j > i; j--) {
if (merged[j] == 0) {
merged[j] = merged[i];
merged[i] = 0;
}
}
}
}
private static void shiftInverse(int[] merged, int n) {
for (int i = 1; i < n; i++) {
for (int j = 0; j < i; j++) {
if (merged[j] == 0) {
merged[j] = merged[i];
merged[i] = 0;
}
}
}
}
shift는 간단하다. 숫자가 있는 칸 기준으로 자기보다 앞에 0이 있으면 가장 앞의 0의 위치로 스왑한다.
이제 주요 기능들이 다 완성되었으니 네 가지 경우의 move만 있으면 된다.
public Board moveLeft() {
int n = this.cells.length;
Board board = new Board(this);
for (int i = 0; i < n; i++) {
int[] merged = mergeInverse(board.cells[i]);
board.cells[i] = merged;
}
return board;
}
public Board moveRight() {
int n = this.cells.length;
Board board = new Board(this);
for (int i = 0; i < n; i++) {
int[] merged = merge(board.cells[i]);
board.cells[i] = merged;
}
return board;
}
public Board moveDown() {
int n = this.cells.length;
Board board = new Board(this);
for (int i = 0; i < n; i++) {
int[] prev = new int[n];
for (int j = 0; j < n; j++) {
prev[j] = board.cells[j][i];
}
int[] merged = merge(prev);
for (int j = 0; j < n; j++) {
board.cells[j][i] = merged[j];
}
}
return board;
}
public Board moveUp() {
int n = this.cells.length;
Board board = new Board(this);
for (int i = 0; i < n; i++) {
int[] prev = new int[n];
for (int j = 0; j < n; j++) {
prev[j] = board.cells[j][i];
}
int[] merged = mergeInverse(prev);
for (int j = 0; j < n; j++) {
board.cells[j][i] = merged[j];
}
}
return board;
}
moveLeft, moveRight는 그냥 바로 merge를 쓰면 된다. (행 단위이므로)
moveUp, moveDown은 2차원 배열의 각 행에서 원하는 열을 뽑아서 진행하는 과정이 추가된다.
static class Board {
final int[][] cells;
Board(int n) {
this.cells = new int[n][n];
}
Board(Board board) {
int n = board.cells.length;
this.cells = new int[n][n];
for (int i = 0; i < n; i++) {
System.arraycopy(board.cells[i], 0, this.cells[i], 0, n);
}
}
public int getMax() {
int max = 0;
for (int i = 0; i < cells.length; i++) {
for (int j = 0; j < cells[i].length; j++) {
max = Math.max(cells[i][j], max);
}
}
return max;
}
...
}
위의 모든 기능은 Board 클래스 단위로 제공한다.
move 메소드들은 Board를 수정하지 않고 새로운 객체를 반환한다. 이는 백트래킹 과정을 쉽게 하기 위함이다.
static int search(Board board, int count) {
if (count == 0) return board.getMax();
int[] results = new int[5];
results[0] = search(board.moveLeft(), count - 1);
results[1] = search(board.moveRight(), count - 1);
results[2] = search(board.moveUp(), count - 1);
results[3] = search(board.moveDown(), count - 1);
results[4] = board.getMax();
int max = 0;
for (int cnt : results)
max = Math.max(max, cnt);
return max;
}
탐색은 브루트포스이므로 간단하다.
재귀로 모든 경우의 수를 탐색하도록 하였다.
이 때 주의할 점으로 최대 5번 이동하였을 때의 최대이므로 현재 상태의 최댓값도 함께 비교해줘야 한다.
main 메소드에서는
System.out.println(search(board, 5));
이렇게 호출하면 솔루션이 완성된다.
깨달은 점
코딩테스트에서 200라인짜리 구현 문제가 막상 펼쳐지면 아찔할 것 같다..
요즘에 구현이 대세인데 이렇게 이동/회전하는 문제들을 조금 더 풀어봐야겠다.
그래도 성취감은 다른 문제들보다 짜릿한 것 같다!
'PS' 카테고리의 다른 글
[BOJ 2150] Strongly Connected Component (Java) (0) | 2022.12.27 |
---|---|
[BOJ 2473] 세 용액 (Java) (1) | 2022.12.27 |
[BOJ 2623] 음악프로그램 (Java) (0) | 2022.12.26 |
[BOJ 14238] 출근 기록 (Java) (0) | 2022.09.12 |
[BOJ 12969] ABC (Java) (0) | 2022.09.11 |