이 영역을 누르면 첫 페이지로 이동
Stranger's LAB 블로그의 첫 페이지로 이동

Stranger's LAB

페이지 맨 위로 올라가기

Stranger's LAB

프로그래밍과 관련하여 다양한 알고리즘 문제를 풀어보고, 프로그래밍 언어를 이해해 볼 수 있도록 돕고자 만든 블로그 입니다.

[백준] 2580번 : 스도쿠 - JAVA [자바]

  • 2020.07.09 22:33
  • JAVA - 백준 [BAEK JOON]/백트래킹
글 작성자: ST_
728x90






www.acmicpc.net/problem/2580

 

2580번: 스도쿠

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

www.acmicpc.net

 









  • 문제




 

 

 

N-Queen 문제를 풀었다면 이 문제 또한 접근 방법만 잘 이해한다면 매우 쉽게 풀 수 있는 문제다.

 

 

 

 

 

 

 

 





  • 알고리즘 [접근 방법]

 



 

스도쿠를 한 번쯤은 모두 접해보셨을 것이라 생각한다. 같은 행과 열에 겹치지 않으면서 3×3 행렬 안에서도 겹치면 안된다. 즉, 이 규칙을 토대로 조건문을 만들어야 한다는 것은 누구나 알 것이다.

 

이전 문제처럼 재귀호출부와 조건 검사 함수, 이렇게 두 개를 구성하도록 하자. 최대한 이해하기 쉽게 주석과 함께 설명하도록 하겠다.

 

 

문제 풀이 접근 방식은 위의 예제를 토대로 설명하겠다.

 

위와 같이 주어졌을 때 (빈칸은 0 으로 주어진다.) 2차원 배열을 활용한다.

즉, int[][] 배열을 사용 할 것인데, 첫 번째 인덱스는 행을, 두 번째 인덱스는 열을 의미한다. ( int[row][col] )

그리고 [0][0] 을 왼쪽 위를 기준으로 할 것이니 이 기준을 이해하고 넘어가자.

 

 

 

[조건식]

 

스도쿠 규칙은 간단하다. 해당 칸에 속한 3×3 배열 안에서 숫자가 겹치지 않으며, 해당 칸과 같은 행과 열 또한 숫자가 겹치지 않아야 한다. 즉, 어떤 값이 해당 칸에 들어갈 수 있는지 여부를 판별해야된다. 이때 탐색하려는 값을 value 라고 정하고 조건식을 만들면 세 가지 조건식을 만들 수 있다.

 

먼저 위 예시를 토대로 (0, 0)부터 검사하도록 해보자.

 

1. 같은 행에 있는 열 원소 중에 겹치는 수가 있는지를 검사한다.

조건식으로 하면 아래와 같을 것이다.

public static boolean possibility(int row, int col, int value) {

	// 같은 행에 있는 원소들 중 겹치는 열 원소가 있는지 검사.
	for (int i = 0; i < 9; i++) {
		if (arr[row][i] == value) {
			return false;
		}
	}
	return true;
}

 

 

 

 

2. 같은 열에 있는 행 원소 중에 겹치는 수가 있는지를 검사한다.

조건식으로 하면 아래와 같다.

public static boolean possibility(int row, int col, int value) {
    
	// 같은 열에 있는 원소들 중 겹치는 행 원소가 있는지 검사
	for (int i = 0; i < 9; i++) {
		if (arr[i][col] == value) {
			return false;
		}
	}
	return true;
}

 

 

 

 

 

3. 해당 위치에 속한 3×3 칸에 중복되는 수가 있는지를 검사한다.

조건식은 다음과 같다.

public static boolean possibility(int row, int col, int value) {

	// 3*3 칸에 중복되는 원소가 있는지 검사
    
	int set_row = (row / 3) * 3;	// value가 속한 3x3의 행의 첫 위치
	int set_col = (col / 3) * 3;	// value가 속한 3x3의 열의 첫 위치

	for (int i = set_row; i < set_row + 3; i++) {
		for (int j = set_col; j < set_col + 3; j++) {
			if (arr[i][j] == value) {
				return false;
			}
		}
	}

	return true;
}

 

여기서 3×3의 위치는 9×9 사이즈에서 3개로 나누면 총 9칸이 생기는데, 각 칸의 위치는 왼쪽 위를 기준으로 할 것이기 때문에 나눗셈을 통해 나머지를 버린 뒤 다시 3을 곱하여 0, 3, 6 중 하나가 나올 수 있도록 한다. 즉, 9개의 칸의 첫 원소의 위치는 다음 9개 중 하나다. ( [0][0], [0][3], [0][6], [3][0], [3][3], [3][6], [6][0], [6][3], [6][6] )

 

 

위 3가지 조건을 모두 통과해야 해당 value 값이 해당 칸에 수가 될 수 있게 된다. 즉, 위 3개의 조건문을 하나로 합치면 다음과 같다. 

public static boolean possibility(int row, int col, int value) {

	// 같은 행에 있는 원소들 중 겹치는 열 원소가 있는지 검사.
	for (int i = 0; i < 9; i++) {
		if (arr[row][i] == value) {
			return false;
		}
	}
    
	// 같은 열에 있는 원소들 중 겹치는 행 원소가 있는지 검사
	for (int i = 0; i < 9; i++) {
		if (arr[i][col] == value) {
			return false;
		}
	}

	// 3*3 칸에 중복되는 원소가 있는지 검사
    
	int set_row = (row / 3) * 3;	// value가 속한 3x3의 행의 첫 위치
	int set_col = (col / 3) * 3;	// value가 속한 3x3의 열의 첫 위치

	for (int i = set_row; i < set_row + 3; i++) {
		for (int j = set_col; j < set_col + 3; j++) {
			if (arr[i][j] == value) {
				return false;
			}
		}
	}

	return true;	// 중복되는 것이 없을 경우 true 반환
}

 

 

 

 

그리고 배열을 (0,0) 부터 순회하며 만약 탐색하는 칸의 값이 0(빈칸)일 경우 반복문을 통해 1 ~ 9 까지의 수들 중 위의 possibility 함수를 통해 만족하게 된다면 재귀호출을 해주는 것이다.

 

 

그럼 위의 내용을 기반으로 코드를 짜보자. 

 

참고로 재귀 호출하면서 모든 값이 다 채워지게 된다면 배열을 출력한 뒤 시스템을 종료해야한다. 아니면 여러개의 스도쿠가 출력된다. (문제에서 나와있듯이 경우의 수가 많을 경우 '한 개'만 출력하라고 명시되어있다) 이 때 시스템을 종료하는 방법은 System.exit(0) 를 사용하면 된다.

 

 

 

 

 

 

 

 

 

 





  • 3가지 방법을 사용하여 풀이한다.

 



알고리즘은 위에서 설명했던 방식 그대로 사용할 것이다. 다만 입출력에서 차이를 두어 각 방법 별 성능차를 보도록 하자.

 

1 . Scanner + System.out.print 반복출력

2 . Scanner + StringBuilder

3. BufferedReader + StringBuilder

 

 

 

 






  • 풀이





- 방법 1 : [Scanner + System.out.print 반복출력]

 

import java.util.Scanner;

public class Main {

	public static int[][] arr = new int[9][9];

	public static void main(String[] args) {

		Scanner in = new Scanner(System.in);

		for (int i = 0; i < 9; i++) {
			for (int j = 0; j < 9; j++) {
				arr[i][j] = in.nextInt();
			}
		}

		sudoku(0, 0);

	}

	public static void sudoku(int row, int col) {

		// 해당 행이 다 채워졌을 경우 다음 행의 첫 번째 열부터 시작
		if (col == 9) {
			sudoku(row + 1, 0);
			return;
		}

		// 행과 열이 모두 채워졌을 경우 출력 후 종료
		if (row == 9) {
			for (int i = 0; i < 9; i++) {
				for (int j = 0; j < 9; j++) {
					System.out.print(arr[i][j] + " ");
				}
				System.out.println();
			}

			// 출력 뒤 시스템을 종료한다.
			System.exit(0);
		}

		// 만약 해당 위치의 값이 0 이라면 1부터 9까지 중 가능한 수 탐색
		if (arr[row][col] == 0) {
			for (int i = 1; i <= 9; i++) {
				// i 값이 중복되지 않는지 검사
				if (possibility(row, col, i)) {
					arr[row][col] = i;
					sudoku(row, col + 1);
				}
			}
			arr[row][col] = 0;
			return;
		}

		sudoku(row, col + 1);

	}

	public static boolean possibility(int row, int col, int value) {

		// 같은 행에 있는 원소들 중 겹치는 열 원소가 있는지 검사
		for (int i = 0; i < 9; i++) {
			if (arr[row][i] == value) {
				return false;
			}
		}

		// 같은 열에 있는 원소들 중 겹치는 행 원소가 있는지 검사
		for (int i = 0; i < 9; i++) {
			if (arr[i][col] == value) {
				return false;
			}
		}

		// 3*3 칸에 중복되는 원소가 있는지 검사
		int set_row = (row / 3) * 3; // value가 속한 3x3의 행의 첫 위치
		int set_col = (col / 3) * 3; // value가 속한 3x3의 열의 첫 위치

		for (int i = set_row; i < set_row + 3; i++) {
			for (int j = set_col; j < set_col + 3; j++) {
				if (arr[i][j] == value) {
					return false;
				}
			}
		}

		return true; // 중복되는 것이 없을 경우 true 반환
	}

}

 

 

가장 기본적인 방법이라 할 수 있겠다.

 

앞서 말했듯 StringBuilder 가 아니라 System.out.print(arr[i] + " "); 로 하게되면 시간초과가 나므로 주의하자.

StringBuilder 대신 BufferedWriter를 써도 된다.

 

 











- 방법 2 : [Scanner + StringBuilder]

 

 

 

배열을 출력할 때 좀 더 효율적으로 출력하기 위해 하나의 문자열로 이어준 뒤, 한 번에 출력하는 방법이다. 이를 위해 StringBuilder 를 사용할 것이다.

 

 

import java.util.Scanner;

public class Main {

	public static int[][] arr = new int[9][9];

	public static void main(String[] args) {

		Scanner in = new Scanner(System.in);

		for (int i = 0; i < 9; i++) {
			for (int j = 0; j < 9; j++) {
				arr[i][j] = in.nextInt();
			}
		}

		sudoku(0, 0);

	}

	public static void sudoku(int row, int col) {

		// 해당 행이 다 채워졌을 경우 다음 행의 첫 번째 열부터 시작
		if (col == 9) {
			sudoku(row + 1, 0);
			return;
		}

		// 행과 열이 모두 채워졌을 경우 출력 후 종료
		if (row == 9) {
			StringBuilder sb = new StringBuilder();
			for (int i = 0; i < 9; i++) {
				for (int j = 0; j < 9; j++) {
					sb.append(arr[i][j]).append(' ');
				}
				sb.append('\n');
			}
			System.out.print(sb);
			// 출력 뒤 시스템을 종료한다.
			System.exit(0);
		}

		// 만약 해당 위치의 값이 0 이라면 1부터 9까지 중 가능한 수 탐색
		if (arr[row][col] == 0) {
			for (int i = 1; i <= 9; i++) {
				// i 값이 중복되지 않는지 검사
				if (possibility(row, col, i)) {
					arr[row][col] = i;
					sudoku(row, col + 1);
				}
			}
			arr[row][col] = 0;
			return;
		}

		sudoku(row, col + 1);

	}

	public static boolean possibility(int row, int col, int value) {

		// 같은 행에 있는 원소들 중 겹치는 열 원소가 있는지 검사
		for (int i = 0; i < 9; i++) {
			if (arr[row][i] == value) {
				return false;
			}
		}

		// 같은 열에 있는 원소들 중 겹치는 행 원소가 있는지 검사
		for (int i = 0; i < 9; i++) {
			if (arr[i][col] == value) {
				return false;
			}
		}

		// 3*3 칸에 중복되는 원소가 있는지 검사
		int set_row = (row / 3) * 3; // value가 속한 3x3의 행의 첫 위치
		int set_col = (col / 3) * 3; // value가 속한 3x3의 열의 첫 위치

		for (int i = set_row; i < set_row + 3; i++) {
			for (int j = set_col; j < set_col + 3; j++) {
				if (arr[i][j] == value) {
					return false;
				}
			}
		}

		return true; // 중복되는 것이 없을 경우 true 반환
	}

}

 

 

 

 

출력이 그렇게 많은 편은 아니라 성능이 유의미하게 차이가 있을련지는 모르겠지만,, 필자는 어지간한 출력에는 대부분 StringBuilder 나 BufferedWriter 을 쓰는 편이다.

 

물론 StringBuilder 대신 BufferedWriter를 써도 되니, 한 번 시도해보는 것도 나쁘지 않다.

 

 











- 방법 3 : [BufferedReader + StringBuilder]

 

 

 

입력 방법을 바꾼 방법이다. 이 때 문자열 분리를 해주어야 할 필요가 있는데, 문자열 분리는 StringTokenizer 을 사용 할 것이다. 물론 이 방법 말고, charAt(j*2) 형식으로 바꾸어 해당 문자에 '0' 이라는 문자를 뺀 값을 넣어주어도 된다. 이는 여러분들이 선호하는 방식대로 해주면 된다.

 

 

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

public class Main {

	public static int[][] arr = new int[9][9];

	public static void main(String[] args) throws IOException {

		BufferedReader br = new BufferedReader(new InputStreamReader(System.in));

		for (int i = 0; i < 9; i++) {
			StringTokenizer st = new StringTokenizer(br.readLine(), " ");
			for (int j = 0; j < 9; j++) {
				arr[i][j] = Integer.parseInt(st.nextToken());
			}
		}

		sudoku(0, 0);

	}

	public static void sudoku(int row, int col) {

		// 해당 행이 다 채워졌을 경우 다음 행의 첫 번째 열부터 시작
		if (col == 9) {
			sudoku(row + 1, 0);
			return;
		}

		// 행과 열이 모두 채워졌을 경우 출력 후 종료
		if (row == 9) {
			StringBuilder sb = new StringBuilder();
			for (int i = 0; i < 9; i++) {
				for (int j = 0; j < 9; j++) {
					sb.append(arr[i][j]).append(' ');
				}
				sb.append('\n');
			}
			System.out.println(sb);
			// 출력 뒤 시스템을 종료한다.
			System.exit(0);
		}

		// 만약 해당 위치의 값이 0 이라면 1부터 9까지 중 가능한 수 탐색
		if (arr[row][col] == 0) {
			for (int i = 1; i <= 9; i++) {
				// i 값이 중복되지 않는지 검사
				if (possibility(row, col, i)) {
					arr[row][col] = i;
					sudoku(row, col + 1);
				}
			}
			arr[row][col] = 0;
			return;
		}

		sudoku(row, col + 1);

	}

	public static boolean possibility(int row, int col, int value) {

		// 같은 행에 있는 원소들 중 겹치는 열 원소가 있는지 검사
		for (int i = 0; i < 9; i++) {
			if (arr[row][i] == value) {
				return false;
			}
		}

		// 같은 열에 있는 원소들 중 겹치는 행 원소가 있는지 검사
		for (int i = 0; i < 9; i++) {
			if (arr[i][col] == value) {
				return false;
			}
		}

		// 3*3 칸에 중복되는 원소가 있는지 검사
		int set_row = (row / 3) * 3; // value가 속한 3x3의 행의 첫 위치
		int set_col = (col / 3) * 3; // value가 속한 3x3의 열의 첫 위치

		for (int i = set_row; i < set_row + 3; i++) {
			for (int j = set_col; j < set_col + 3; j++) {
				if (arr[i][j] == value) {
					return false;
				}
			}
		}

		return true; // 중복되는 것이 없을 경우 true 반환
	}

}

 

 

 

크게 어려울 것은 없을 것이다. 

 

 

 

 

 

 

 

 

 





  • 성능






 

채점 번호 : 20835695  -  방법 3 : BufferedReader + StringBuilder

채점 번호 : 20835691  -  방법 2 : Scanner + StringBuilder

채점 번호 : 20835688  -  방법 1 : Scanner + System.out.print 반복 출력

 

 

 








  • 정리

 



이 번 문제 또한 그리 어려운 문제는 아니였다. 그런데 정답 비율이 높지 않은 것을 보면 아마 재귀호출을 잘못하지 않았을까 싶다. 이러한 백트래킹 문제는 '조건'과 '재귀'를 정확하게 작성해주지 않으면 자칫 재귀가 깊어지거나 이상하게 튀어버릴 수 있기 때문에 반드시 구상을 먼저 정확하게 해야 한다.

 

가장 추천하는 방법은 디버깅을 해보는 것이 좋다.

 

혹여 이 문제가 이해가 되지 않거나 설명이 부족한 부분이 있다면 댓글 남겨주시면 최대한 빠르게 답변드리겠다.

 

 

 

 



저작자표시 비영리 변경금지 (새창열림)

'JAVA - 백준 [BAEK JOON] > 백트래킹' 카테고리의 다른 글

[백준] 14889번 : 스타트와 링크 - JAVA [자바]  (42) 2020.07.15
[백준] 14888번 : 연산자 끼워넣기 - JAVA [자바]  (36) 2020.07.14
[백준] 9663번 : N-Queen - JAVA [자바]  (51) 2020.07.08
[백준] 15652번 : N과 M (4) - JAVA [자바]  (4) 2020.07.03
[백준] 15651번 : N과 M (3) - JAVA [자바]  (4) 2020.06.29

댓글

이 글 공유하기

  • 구독하기

    구독하기

  • 카카오톡

    카카오톡

  • 라인

    라인

  • 트위터

    트위터

  • Facebook

    Facebook

  • 카카오스토리

    카카오스토리

  • 밴드

    밴드

  • 네이버 블로그

    네이버 블로그

  • Pocket

    Pocket

  • Evernote

    Evernote

다른 글

  • [백준] 14889번 : 스타트와 링크 - JAVA [자바]

    [백준] 14889번 : 스타트와 링크 - JAVA [자바]

    2020.07.15
  • [백준] 14888번 : 연산자 끼워넣기 - JAVA [자바]

    [백준] 14888번 : 연산자 끼워넣기 - JAVA [자바]

    2020.07.14
  • [백준] 9663번 : N-Queen - JAVA [자바]

    [백준] 9663번 : N-Queen - JAVA [자바]

    2020.07.08
  • [백준] 15652번 : N과 M (4) - JAVA [자바]

    [백준] 15652번 : N과 M (4) - JAVA [자바]

    2020.07.03
다른 글 더 둘러보기

정보

Stranger's LAB 블로그의 첫 페이지로 이동

Stranger's LAB

  • Stranger's LAB의 첫 페이지로 이동

검색

나의 외부 링크

  • st-github

공지사항

  • 공지 - 블로그 사용 설명서

메뉴

  • 홈
  • 방명록

카테고리

  • 전체 카테고리 (267)
    • Java (5)
    • JAVA - 백준 [BAEK JOON] (177)
      • 입출력과 사칙연산 (14)
      • 조건문 (7)
      • 반복문 (11)
      • 1차원 배열 (7)
      • 함수 (3)
      • 문자열 (10)
      • 기본 수학 1 (8)
      • 기본 수학 2 (6)
      • 2차원 배열 (0)
      • 정렬 (10)
      • 재귀 (4)
      • 브루트 포스 (5)
      • 집합과 맵 (0)
      • 기하 1 (5)
      • 정수론 및 조합론 (12)
      • 백트래킹 (8)
      • 동적 계획법 1 (15)
      • 누적 합 (0)
      • 그리디 알고리즘 (5)
      • 스택 (5)
      • 큐, 덱 (7)
      • 분할 정복 (9)
      • 이분 탐색 (7)
      • 기타 문제 (17)
      • 별 찍기 문제 모음 (2)
    • C++ - 백준 [BAEK JOON] (46)
      • 입출력과 사칙연산 (14)
      • 조건문 (7)
      • 반복문 (11)
      • 1차원 배열 (7)
      • 함수 (3)
      • 문자열 (0)
      • 기타 문제 (4)
    • 자료구조 (18)
      • Java (18)
    • 알고리즘 (11)
      • Java (11)
    • 프로그래밍 기초 (6)
    • 이모저모 (2)
    • 일상의 글 (2)

최근 글

정보

ST_의 Stranger's LAB

Stranger's LAB

ST_

블로그 구독하기

  • 구독하기
  • 네이버 이웃 맺기
  • RSS 피드

방문자

  • 전체 방문자
  • 오늘
  • 어제

티스토리

  • 티스토리 홈
  • 이 블로그 관리하기
  • 글쓰기
Powered by Tistory / Kakao. Copyright © ST_.

티스토리툴바