본문 바로가기

코딩 테스트(Coding test)/BOJ

[BOJ 2580번/C++] 스도쿠

미숙한 블로그 주인이 코딩테스트 문제를 풀어가는 과정을 담은 글입니다. 이 풀이가 효율적인 풀이가 아닐 수 있으며, 부정확한 정보가 많이 있을 수 있습니다. 보완해야할 점이 있다면 댓글로 남겨주세요!

 

https://www.acmicpc.net/problem/2580

 

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

 

풀이

빈칸마다 숫자를 하나씩 넣어보면서 조건에 맞는 수인지를 계속 확인해나가는 백트래킹 문제였다.

 

스도쿠는 가로줄, 세로줄, 3X3 영역에서 중복되는 수가 없어야한다.

그래서, 빈칸에 우선 수를 하나 넣어보고 위에서 말한 조건에 위배되는지를 파악한다.

 

위배된다면 다시 빈칸으로 만들고 다른 수로 알아보면 되고,위배되지않는다면 다음 위치를 찾아 수를 대입해보면 된다.

 

문제에서 스도쿠를 채우는 방법이 여러가지이면 하나만을 출력하라고 했으니, 이미 방법을 찾았는지를 판별하는 변수를 선언해 바로 리턴해주는 문구도 작성해줘야한다.

 

#include <iostream>
#include <vector>

using namespace std;

vector<vector<int>> sudoku(9, vector<int>(9));
vector<pair<int, int>> zeropos;
int zerocnt = 0;
bool is_find = false;

bool check(int& low, int& col) {
    for (int i = 0; i < 9; i++) {
        // 가로줄에서 일치하는 것이 있는지 판별
        if (i != col && sudoku[low][i] == sudoku[low][col]) {
            return false;
        }
        // 세로줄에서 일치하는 것이 있는지 판별
        if (i != low && sudoku[i][col] == sudoku[low][col]) {
            return false;
        }
    }

    // 3x3 영역에서 일치하는 것이 있는지 판별
    int tmp_1 = 3 * (int)(low / 3);
    int tmp_2 = 3 * (int)(col / 3);

    for (int i = tmp_1; i < tmp_1 + 3; i++) {
        for (int j = tmp_2; j < tmp_2 + 3; j++) {
            if (i != low && j != col) {
                if (sudoku[i][j] == sudoku[low][col]) {
                    return false;
                }
            }
        }
    }

    return true;
}

void solution(int idx) {
    // 빈칸을 모두 채우면 출력
    if (idx == zerocnt) {
        for (int i = 0; i < 9; i++) {
            for (int j = 0; j < 9; j++) {
                cout << sudoku[i][j] << " ";
            }
            cout << "\n";
        }

        is_find = true;
        return;
    }

    // 이미 찾았으면 더 이상 찾을 필요 없으니 바로 반환
    if (is_find)
        return;

    int x = zeropos[idx].first;
    int y = zeropos[idx].second;

    // 하나씩 집어넣어보며 판별해나가기.
    for (int i = 1; i < 10 && !is_find; i++) {
        sudoku[x][y] = i;
        if (check(x, y)) {
            solution(idx + 1);
        }
        sudoku[x][y] = 0;
    }
}

int main() {
    for (int i = 0; i < 9; i++) {
        for (int j = 0; j < 9; j++) {
            cin >> sudoku[i][j];
            if (!sudoku[i][j]) {
                zeropos.push_back({ i, j });
                zerocnt++;
            }
        }
    }

    solution(0);

    return 0;
}