JAVA 알고리즘/유형 문제

[백준 2048 java 자바]2048 - DFS,구현

nomoreFt 2022. 10. 9. 18:55

[Gold II] 2048 (Easy) - 12100

문제 링크

성능 요약

메모리: 20500 KB, 시간: 188 ms

분류

백트래킹(backtracking), 브루트포스 알고리즘(bruteforcing), 구현(implementation), 시뮬레이션(simulation)

문제 설명

2048 게임은 4×4 크기의 보드에서 혼자 즐기는 재미있는 게임이다. 이 링크를 누르면 게임을 해볼 수 있다.

이 게임에서 한 번의 이동은 보드 위에 있는 전체 블록을 상하좌우 네 방향 중 하나로 이동시키는 것이다. 이때, 같은 값을 갖는 두 블록이 충돌하면 두 블록은 하나로 합쳐지게 된다. 한 번의 이동에서 이미 합쳐진 블록은 또 다른 블록과 다시 합쳐질 수 없다. (실제 게임에서는 이동을 한 번 할 때마다 블록이 추가되지만, 이 문제에서 블록이 추가되는 경우는 없다)

<그림 1> <그림 2> <그림 3>

<그림 1>의 경우에서 위로 블록을 이동시키면 <그림 2>의 상태가 된다. 여기서, 왼쪽으로 블록을 이동시키면 <그림 3>의 상태가 된다.

<그림 4> <그림 5> <그림 6> <그림 7>

<그림 4>의 상태에서 블록을 오른쪽으로 이동시키면 <그림 5>가 되고, 여기서 다시 위로 블록을 이동시키면 <그림 6>이 된다. 여기서 오른쪽으로 블록을 이동시켜 <그림 7>을 만들 수 있다.

<그림 8> <그림 9>

<그림 8>의 상태에서 왼쪽으로 블록을 옮기면 어떻게 될까? 2가 충돌하기 때문에, 4로 합쳐지게 되고 <그림 9>의 상태가 된다.

<그림 10> <그림 11> <그림 12> <그림 13>

<그림 10>에서 위로 블록을 이동시키면 <그림 11>의 상태가 된다.

<그림 12>의 경우에 위로 블록을 이동시키면 <그림 13>의 상태가 되는데, 그 이유는 한 번의 이동에서 이미 합쳐진 블록은 또 합쳐질 수 없기 때문이다.

<그림 14> <그림 15>

마지막으로, 똑같은 수가 세 개가 있는 경우에는 이동하려고 하는 쪽의 칸이 먼저 합쳐진다. 예를 들어, 위로 이동시키는 경우에는 위쪽에 있는 블록이 먼저 합쳐지게 된다. <그림 14>의 경우에 위로 이동하면 <그림 15>를 만든다.

이 문제에서 다루는 2048 게임은 보드의 크기가 N×N 이다. 보드의 크기와 보드판의 블록 상태가 주어졌을 때, 최대 5번 이동해서 만들 수 있는 가장 큰 블록의 값을 구하는 프로그램을 작성하시오.

입력

첫째 줄에 보드의 크기 N (1 ≤ N ≤ 20)이 주어진다. 둘째 줄부터 N개의 줄에는 게임판의 초기 상태가 주어진다. 0은 빈 칸을 나타내며, 이외의 값은 모두 블록을 나타낸다. 블록에 쓰여 있는 수는 2보다 크거나 같고, 1024보다 작거나 같은 2의 제곱꼴이다. 블록은 적어도 하나 주어진다.

출력

최대 5번 이동시켜서 얻을 수 있는 가장 큰 블록을 출력한다.


풀이

개인적으로 난이도가 좀 높았다고 생각했다. dfs 말고 구현부분에서.

여러 사람들의 답을 보고, 제일 이해가고 간결한 구현을 참고했다.

  1. 큰 틀에서 dfs는 5번 이동이므로 depth 5번의 dfs를 구현한다.
  2. depth 5에서의 경우마다 map 전체를 돌아가며 가장 큰 수를 구해 답을 구한다.
  3. map은 재활용하며 모든 경우를 따지기 위해 depth가 깊어지는 다음 단계 전에 visited를 true로 방문 표시하고 나와서 false로 초기화 해주는 것 처럼, map을 움직이게 바꾸고 depth 빠져 나와서 다시 이전 상태로 초기화한다. (temp 그래프를 만들고 clone)
  4. 위,아래,왼,오 경우에 따라 밀어서 다음 경우를 따진다.(구현에서 제일 중요)
  • 중요 사항
depth = 왼,오,아래로 밀기 depth3 처럼 몇 번 밀었는가,  


map으로 변화 줄 경우, 되돌려서 재활용 하기  


stream으로 split(" ")의 경우 초기화 하기

for(int i = 0; i < N; i++){ stream(br.readLine().split(" ")) .mapToInt(Integer::parseInt).toArray(); }


코드

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.LinkedList;

class Main {
    static int N;
    static int[][] graph;//전체 지도
    static int result = 0;//정답(최대값)
    public static void main(String[] args) throws IOException{
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        N = Integer.parseInt(br.readLine());

        graph = new int[N][N];
        for(int i = 0; i < N; i++){
            stream(br.readLine().split(" "))
                    .mapToInt(Integer::parseInt).toArray();
        }
        ///초기화


        dfs(0);
        System.out.println(result);
    }
    static void dfs(int depth) {
        if (depth == 5) {//1.depth(5가지 위아래왼오 경우로 민 상황)가 5인 경우 결과값 구하기
            findMax();
            return;
        }

        int[][] temp = new int[N][N];
        for (int i = 0; i < N; i++) {//3.visited 초기화처럼 다음 경우의 수 이전에 map 옮긴 것을 미리 담아뒀다가 복구
            temp[i] = graph[i].clone();
        }
        for (int i = 0; i < 4; i++) {
            move(i);
            dfs(depth + 1);
            for (int j = 0; j < N; j++) {
                graph[j] = temp[j].clone();
            }
        }
    }

    private static void findMax() {
        for (int i = 0; i < N; i++) {
            for (int j = 0; j < N; j++) {
                result = Math.max(result, graph[i][j]);
            }
        }
    }

    static void move(int dir) {
        // 0: 왼쪽, 1: 오른쪽, 2: 위쪽, 3: 아래쪽
        LinkedList<Integer> q = new LinkedList<>(); //4. 경우 따지기
        if (dir == 0) {
            for (int i = 0; i < N; i++) {// 행 반복

                //0으로 초기화, Q에 다 집어넣기 (한 숫자 당, 한 번만 동작하면 되므로)
                for (int j = 0; j < N; j++) {
                    if (graph[i][j] != 0) {
                        q.add(graph[i][j]);//왼쪽으로 밀 때, 왼쪽 첫번째 숫자부터 (0,0)(0,1)(0,2) 순으로 큐에 넣는다.(왼쪽 첫 번째부터)
                        graph[i][j] = 0;//그리고 0으로 초기화
                    }
                }

                //Q에서 하나씩 꺼내서, 0이면 넣고, 0이 아니면, 이전 값과 비교해서 같으면 더하고, 다르면 넣기
                int idx = 0; //graph에서 숫자 넣을 자리 target
                while (!q.isEmpty()) {
                    int now = q.poll();//q에서 계속 꺼내 비교, q가 비면 종료
                    if (graph[i][idx] == 0) {//0으로 다 초기화 해놨기 때문에, 처음의 경우 초기화
                        graph[i][idx] = now;
                    } else if (graph[i][idx] == now) {//이전에 저장해둔 초기화 값==다음 꺼낸 수 (now) 이면, *2해서 넣기
                        graph[i][idx] *= 2;
                        idx++;
                    }else{//안같으면 그냥 합칠게 아닌거니까 꺼낸 지금 now숫자 그대로 넣어주기
                        idx++;
                        graph[i][idx] = now;
                    }
                }
            }
        } else if (dir == 1) { //오른쪽
            for (int i = 0; i < N; i++) {
                for (int j = N-1; j >=0; j--) {
                    if (graph[i][j] != 0) {
                        q.add(graph[i][j]);
                        graph[i][j] = 0;
                    }
                }
                int idx = N - 1;
                while (!q.isEmpty()) {
                    int cur = q.poll();

                    if (graph[i][idx] == 0) {
                        graph[i][idx] = cur;
                    } else if (graph[i][idx] == cur) {
                        graph[i][idx] *= 2;
                        idx--;
                    }else{
                        idx--;
                        graph[i][idx] = cur;
                    }
                }
            }
        } else if (dir == 2) { //위쪽
            for (int i = 0; i < N; i++) {
                for (int j = 0; j < N; j++) {
                    if (graph[j][i] != 0) {
                        q.add(graph[j][i]);
                        graph[j][i] = 0;
                    }
                }
                int idx = 0;
                while (!q.isEmpty()) {
                    int cur = q.poll();
                    if (graph[idx][i] == 0) {
                        graph[idx][i] = cur;
                    } else if (graph[idx][i] == cur) {
                        graph[idx][i] *= 2;
                        idx++;
                    }else{
                        idx++;
                        graph[idx][i] = cur;
                    }
                }
            }
        }
        else {
                for (int i = 0; i < N; i++) {
                for (int j = N-1; j >= 0; j--) {
                    if (graph[j][i] != 0) {
                        q.add(graph[j][i]);
                        graph[j][i] = 0;
                    }
                }
                int idx = N - 1;
                while (!q.isEmpty()) {
                    int cur = q.poll();
                    if (graph[idx][i] == 0) {
                        graph[idx][i] = cur;
                    } else if (graph[idx][i] == cur) {
                        graph[idx][i] *= 2;
                        idx--;
                    }else{
                        idx--;
                        graph[idx][i] = cur;
                    }
                }
            }
        }
    }
}