Gold
문제
스도쿠는 18세기 스위스 수학자가 만든 '라틴 사각형'이랑 퍼즐에서 유래한 것으로 현재 많은 인기를 누리고 있다.
이 게임은 아래 그림과 같이 가로, 세로 각각 9개씩 총 81개의 작은 칸으로 이루어진 정사각형 판 위에서 이뤄지는데, 게임 시작 전 일부 칸에는 1부터 9까지의 숫자 중 하나가 쓰여 있다.
나머지 빈 칸을 채우는 방식은 다음과 같다.
- 각각의 가로줄과 세로줄에는 1부터 9까지의 숫자가 한 번씩만 나타나야 한다.
- 굵은 선으로 구분되어 있는 3x3 정사각형 안에도 1부터 9까지의 숫자가 한 번씩만 나타나야 한다.
위의 예의 경우, 첫째 줄에는 1을 제외한 나머지 2부터 9까지의 숫자들이 이미 나타나 있으므로 첫째 줄 빈칸에는 1이 들어가야 한다.
또한 위쪽 가운데 위치한 3x3 정사각형의 경우에는 3을 제외한 나머지 숫자들이 이미 쓰여있으므로 가운데 빈 칸에는 3이 들어가야 한다.
이와 같이 빈 칸을 차례로 채워 가면 다음과 같은 최종 결과를 얻을 수 있다.
게임 시작 전 스도쿠 판에 쓰여 있는 숫자들의 정보가 주어질 때 모든 빈 칸이 채워진 최종 모습을 출력하는 프로그램을 작성하시오.
입력
아홉 줄에 걸쳐 한 줄에 9개씩 게임 시작 전 스도쿠판 각 줄에 쓰여 있는 숫자가 한 칸씩 띄워서 차례로 주어진다.
스도쿠 판의 빈 칸의 경우에는 0이 주어진다.
스도쿠 판을 규칙대로 채울 수 없는 경우의 입력은 주어지지 않는다.
출력
모든 빈 칸이 채워진 스도쿠 판의 최종 모습을 아홉 줄에 걸쳐 한 줄에 9개씩 한 칸씩 띄워서 출력한다.
스도쿠 판을 채우는 방법이 여럿인 경우는 그 중 하나만을 출력한다.
풀이
import java.util.*;
import java.io.*;
class Main {
static int[][] maps = new int[9][9];
static List<int[]> list = new ArrayList<>();
static boolean solve = false;
public static void main(String[] args) throws IOException {
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
StringTokenizer st;
for(int i=0; i<9; i++){
st = new StringTokenizer(br.readLine());
for(int j=0; j<9; j++){
maps[i][j] = Integer.parseInt(st.nextToken());
// 빈 칸의 경우만 list에 따로 좌표를 저장
if(maps[i][j] == 0) list.add(new int[] {i, j});
}
}
dfs(0);
StringBuilder sb = new StringBuilder();
for(int i=0; i<9; i++){
for(int j=0; j<9; j++){
sb.append(maps[i][j] + " ");
}
sb.append("\n");
}
System.out.println(sb);
}
static void dfs(int index){
// 빈 칸을 모두 채운 경우 탐색 종료 (백트래킹)
if(index == list.size()){
solve = true;
return;
}
int[] pos = list.get(index);
int x = pos[0];
int y = pos[1];
for(int i=1; i<=9; i++){
if(checkAll(x, y, i)){
maps[x][y] = i;
dfs(index+1);
if (solve) return; // 모든 빈칸을 채운 경우 종료
maps[x][y] = 0;
}
}
}
static boolean checkAll(int row, int col, int val){
boolean flag;
flag = checkCol(col, val); // 열 검사
if(flag) flag = checkRow(row, val); // 행 검사
if(flag) flag = checkBox(row, col, val); // 박스 검사
return flag;
}
// 행 검사
static boolean checkRow(int row, int val){
for(int i=0; i<9; i++){
if(maps[row][i]==val)
return false;
}
return true;
}
// 열 검사
static boolean checkCol(int col, int val){
for(int i=0; i<9; i++){
if(maps[i][col]==val)
return false;
}
return true;
}
// 박스 검사
static boolean checkBox(int row, int col, int val){
int x = row/3*3;
int y = col/3*3;
for(int i=x; i<x+3; i++){
for(int j=y; j<y+3; j++){
if(maps[i][j]==val) return false;
}
}
return true;
}
}
해결방법
- 9x9 배열을 생성하여 스도쿠를 입력받아 저장한다.
- 값이 0인 좌표만 따로 list에 저장해둔다
- DFS 탐색을 통해 스토쿠 빈칸을 채워나간다.
- dfs(index) 함수를 통해 리스트를 순서대로 탐색한다.
- 빈칸에 가능한 숫자를 하나씩 대입하고, 해당 값이 유효성을 검사한다
-> checkRow, checkCol, checkBox 함수로 각각 행, 열, 박스 안에서 해당 숫자의 중복 여부를 검사한다. - 유효성이 검증되었을 경우 재귀적으로 다음 빈칸으로 이동한다
- 모든 빈칸을 채웠을 경우 (list의 모든 값을 탐색했을 경우) solve를 true로 설정해 탐색을 종료한다.
사용한 자료구조
- DFS (깊이 우선 탐색): 빈칸을 재귀적으로 탐색할 수 있으며, DFS는 깊이를 먼저 탐색할 수 있다.
스토쿠는 단일 해를 찾는 문제이며, DFS는 해를 찾자마자 탐색을 종료할 수 있기 때문에 가능한 모든 탐색을 하는 BFS보다 효율적이다.
https://www.acmicpc.net/problem/2580
'Algorithm > Baekjoon Oline Judge - Java' 카테고리의 다른 글
[백준] 나머지합 / 누적합 (1) | 2024.12.08 |
---|---|
[백준] 1835_카드 / 덱 deque (2) | 2024.12.07 |
[백준] 1816_암호키 / 큰 소수, 6k ± 1 (0) | 2024.12.04 |
[백준] 1526_가장큰금민수 / DFS 조합 (0) | 2024.11.10 |
[백준] 1072_게임 / 이분탐색 (0) | 2024.11.03 |