본문 바로가기
PS/백준

[백준 *Java] - 스도쿠(2580)

by Jman 2022. 5. 11.

스도쿠

 

2580번: 스도쿠

스도쿠는 18세기 스위스 수학자가 만든 '라틴 사각형'이랑 퍼즐에서 유래한 것으로 현재 많은 인기를 누리고 있다. 이 게임은 아래 그림과 같이 가로, 세로 각각 9개씩 총 81개의 작은 칸으로 이루

www.acmicpc.net

문제

스도쿠는 18세기 스위스 수학자가 만든 '라틴 사각형'이랑 퍼즐에서 유래한 것으로 현재 많은 인기를 누리고 있다. 이 게임은 아래 그림과 같이 가로, 세로 각각 9개씩 총 81개의 작은 칸으로 이루어진 정사각형 판 위에서 이뤄지는데, 게임 시작 전 일부 칸에는 1부터 9까지의 숫자 중 하나가 쓰여 있다.

나머지 빈 칸을 채우는 방식은 다음과 같다.

  1. 각각의 가로줄과 세로줄에는 1부터 9까지의 숫자가 한 번씩만 나타나야 한다.
  2. 굵은 선으로 구분되어 있는 3x3 정사각형 안에도 1부터 9까지의 숫자가 한 번씩만 나타나야 한다.

위의 예의 경우, 첫째 줄에는 1을 제외한 나머지 2부터 9까지의 숫자들이 이미 나타나 있으므로 첫째 줄 빈칸에는 1이 들어가야 한다.

또한 위쪽 가운데 위치한 3x3 정사각형의 경우에는 3을 제외한 나머지 숫자들이 이미 쓰여있으므로 가운데 빈 칸에는 3이 들어가야 한다.

이와 같이 빈 칸을 차례로 채워 가면 다음과 같은 최종 결과를 얻을 수 있다.

게임 시작 전 스도쿠 판에 쓰여 있는 숫자들의 정보가 주어질 때 모든 빈 칸이 채워진 최종 모습을 출력하는 프로그램을 작성하시오.

입력

아홉 줄에 걸쳐 한 줄에 9개씩 게임 시작 전 스도쿠판 각 줄에 쓰여 있는 숫자가 한 칸씩 띄워서 차례로 주어진다. 스도쿠 판의 빈 칸의 경우에는 0이 주어진다. 스도쿠 판을 규칙대로 채울 수 없는 경우의 입력은 주어지지 않는다.

출력

모든 빈 칸이 채워진 스도쿠 판의 최종 모습을 아홉 줄에 걸쳐 한 줄에 9개씩 한 칸씩 띄워서 출력한다.

스도쿠 판을 채우는 방법이 여럿인 경우는 그 중 하나만을 출력한다.

제한

  • 12095번 문제에 있는 소스로 풀 수 있는 입력만 주어진다.
    • C++14: 80ms
    • Java: 292ms
    • PyPy3: 1172ms

 

👇 접근방식 

문제를 볼 때, 백트래킹으로 생각을하면 되겠다고 생각을 했습니다.
일단 스도쿠를 풀어보셨거나? 무엇인 지 안다면 규칙을 알 수 있습니다.
row(가로), column(세로), 3By3 Martrix(행렬) 이 1부터 9까지 중복없이 수가 들어가야한다는 규칙입니다.
그래서, 문제를 풀 땐 백트래킹으로 Martix 에 빈 곳(0)을 기준으로 가로(1~9), 세로(1~9), 3x3Matrix(1~9) 에 있는 수 중에서 없는 수를 뽑아서 그 수를 가지고 백트래킹을 시도하는 방향으로 짠다고 생각을 했습니다.

예시 예제입력1 기준.
x,y(0,0) 위치에 빈 곳(0)이 있으니 그걸로 설명을 해보겠습니다.
1,2,3,4,5,6,7,8,9 중에서
남은 수 중에서 0행의 위치에 있는 숫자(7,3,8,5,9,6,2)를 지웁니다.
1,4
남은 수 중에서 0열의 위치에 있는 숫자(4)를 지웁니다.
1
남은 수 중에서 3by3 위치에 있는 숫자(x)를 지웁니다.
결과는 1 입니다.
이런 식으로 결과값이 여러 개인 경우는 백트래킹(가지치기)를 이용하여 값을 찾아가는 방식으로 로직을 설계하면 될 거 같습니다.

그리고 3By3 Matrix에 있는 1~9까지 값을 찾기 위해서는 나머지를 이용해서 3By3 Matrix 위치 내에 있는 숫자를 탐색할 수 있게끔 값을 변환해준 뒤 for문 루프를 돌게 끔 구현해주면 됩니다.

 

아래는 성공한 코드입니다

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

public class Boj_2580 {
    static final int MATRIX_SIZE = 9;
    static int[][] board = new int[MATRIX_SIZE][MATRIX_SIZE];

    static void printBoard() {
        StringBuilder sb = new StringBuilder();
        for (int row = 0; row < MATRIX_SIZE; row++) {
            for (int column = 0; column < MATRIX_SIZE; column++) {
                sb.append(board[row][column]).append(" ");
            }
            sb.append("\n");
        }

        System.out.println(sb);
    }

    // Row(행) 를 확인합니다.
    static boolean isNumberInRow(int number, int row) {
        for (int i = 0; i < MATRIX_SIZE; i++) {
            if (board[row][i] == number) {
                return true;
            }
        }
        return false;
    }
    // Col(열) 를 확인합니다.
    static boolean isNumberInColumn(int number, int column) {
        for (int i = 0; i < MATRIX_SIZE; i++) {
            if (board[i][column] == number) {
                return true;
            }
        }
        return false;
    }

    // 3x3 매트릭스 내에 수가 있는지 확인 메서드
    static boolean isNumberIn3By3Matrix(int number, int row, int column) {
        int start3By3MatrixRow = row - (row % 3);
        int start3By3MatrixColumn = column - (column % 3);
        // ex (5,3) 일 경우, (3,3) 으로 변환하는 과정
        for (int i = start3By3MatrixRow; i < start3By3MatrixRow + 3; i++) {
            for (int j = start3By3MatrixColumn; j < start3By3MatrixColumn + 3; j++) {
                if (board[i][j] == number) {
                    return true;
                }
            }
        }
        return false;
    }

    // 세 조건이 유효한 지 확인하는 메서드
    static boolean isValid(int number, int row, int column) {
        return !isNumberInRow(number, row) &&
                !isNumberInColumn(number, column) &&
                !isNumberIn3By3Matrix(number, row, column);
    }

    // 스도쿠 풀이를 위한 메서드
    static boolean dfs() {
        // 이중 for loop 를 돌면서,
        for (int row = 0; row < MATRIX_SIZE; row++) {
            for (int column = 0; column < MATRIX_SIZE; column++) {

                // 스도쿠 map 에 빈 곳(0)이라면? In
                if (board[row][column] == 0) {
                    for (int i = 1; i <= MATRIX_SIZE; i++) {
                        if (isValid(i, row, column)) { // 세 가지 조건이 true 라면?
                            board[row][column] = i; // 그 타입을 저장
                            if (dfs()) return true;
                            else board[row][column] = 0; // backtracking
                        }
                    }
                    // * 스도쿠를 해결할 수 없음. 따라서 return false.
                    return false;
                }
            }
        }
        return true;
    }

    public static void main(String[] args) throws IOException {
        BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
        StringTokenizer st;
        for (int i = 0; i < MATRIX_SIZE; i++) {
            st = new StringTokenizer(br.readLine());
            for (int j = 0; j < MATRIX_SIZE; j++) {
                board[i][j] = Integer.parseInt(st.nextToken());
            }
        }
        dfs();
        printBoard();
    }
}

'PS > 백준' 카테고리의 다른 글

[백준 *Java] - 가르침(1062)  (0) 2022.05.15
[백준 *Java] - 알파벳(1987)  (0) 2022.05.12
[백준 *Java] - N-Queen (9663)  (0) 2022.05.09
[백준 *Java] - 에너지 모으기(16198)  (0) 2022.05.08
[백준 *Java] - 두 동전(16197)  (0) 2022.05.07