[JAVA/백준] 16571번: 알파 틱택토

👀 문제

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

👊 도전

1. 설계

  1. map에서 0 개수가 홀수면 1부터, 짝수면 2부터 시작한다.
  2. game()에 누구 차례인지 나타내는 now와 배열 map을 넘겨줘서 게임을 진행한다.
  3. 0인 위치를 찾고, now가 그 자리를 선택하는 경우와 아닌 경우로 나눠서 문제를 푼다.
  4. 선택한다면 (i, j)에 now를 넣고 isWin()으로 이기는 지 체크한다.
  5. game() 재귀 호출로 다음 게임으로 넘어간다.
  6. 재귀 복귀 후에는 (i, j)=0으로 초기화하여 선택하지 않는 경우를 생각한다.

2. 구현 (성공 코드)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
import java.util.*;
import java.io.*;
/**
 * @author HEESOO
 *
 */
class Main {
	public static void main(String[] args) throws IOException {
		BufferedReader br=new BufferedReader(new InputStreamReader(System.in));
		StringTokenizer st;		
		
		int[][] map=new int[3][3];
		int zero=0; // 맵에서 0개수 카운트
		
		for(int i=0;i<3;i++) {
			st=new StringTokenizer(br.readLine());
			for(int j=0;j<3;j++) {
				map[i][j]=Integer.parseInt(st.nextToken());
				if(map[i][j]==0) zero++;
			}
		}
		
		// 0이 홀수면 1, 짝수면 2부터 시작
		int win=game(zero%2!=0? 1:2, map);
		char answer;
		if(win==1) answer='W';
		else if(win==0) answer='D';
		else answer='L';
		System.out.println(answer);
	}
	
	public static int game(int now, int[][] map) {
		int min=2; // 상대방의 최선의 승패를 저장
		for(int i=0;i<3;i++) {
			for(int j=0;j<3;j++) {
				if(map[i][j]==0) { // 놓을 수 있는 위치
					map[i][j]=now; // 여기에 now가 놓는다
					// (i,j)에 놓을 때 내가 이길 수 있는 경우라면 상대방은 지므로 -1
					if(isWin(now, map)) min=Math.min(min, -1);
					// 다음 차례를 재귀로 호출후 그 결과를 min에 저장
					min=Math.min(min, game(now==1? 2:1, map));
					// 내가 (i, j)에 놓지 않는 경우
					map[i][j]=0;
				}
			}
		}
		
		// min은 상대방의 승패를 저장한 것이므로 나의 승패는 반대로 리턴해줘야 함
		if(min==1) return -1;
		else if(min==0 || min==2) return 0;
		else return 1;
	}
	
	public static boolean isWin(int now, int[][] map) { // 현재 map에서 now가 이길 수 있는가를 T/F로 리턴
		for(int i=0;i<3;i++) {
			if(map[i][0]==now && map[i][0]==map[i][1] && map[i][1]==map[i][2]) return true;
		}
		
		for(int j=0;j<3;j++) {
			if(map[0][j]==now && map[0][j]==map[1][j] && map[1][j]==map[2][j]) return true;
		}
		
		if(map[0][0]==now && map[0][0]==map[1][1] && map[1][1]==map[2][2]) return true;
		if(map[0][2]==now && map[0][2]==map[1][1] && map[1][1]==map[2][0]) return true;
		
		return false;
	}
}

3. 결과

실행결과 🤟 성공 🤟

4. 설명

  1. map을 생성하고, 누구부터 시작하는지 판별하여 game()을 호출한다
    • map을 만들 때 0의 개수를 카운트하여 0이 홀수면 1부터, 짝수면 2부터 game()을 호출한다.
    • game()은 int형을 리턴하는데, 이때 리턴 값은 처음 시작한 사람이 이긴다면 1, 무승부면 0, 지면 -1을 가지고 돌아온다.
  2. 재귀로 모든 경우를 체크한다
    • game()의 리턴값은 now의 승패를 리턴한다.
    • min은 상대방의 승패를 저장한다.
    • 최선의 승패를 리턴해야 하므로 now가 지는 것은 피해야 한다.
    • 따라서 min은 상대방의 승패이므로 상대가 지는게 가장 좋고, 다음은 비기고, 마지막은 이기는 것이다. 즉, min의 우선순위가 -1, 0, 1 순이므로 Math.min()을 이용한다.
    • 이중 for문으로 맵을 순회한다. 0인 곳을 찾아서 이곳에 now을 놓을지 말지 체크해야 한다.
    • 처음은 놓는다고 가정한다(map[i][j]=now).
    • 놓고 난 다음 isWin()으로 now의 승패를 체크한다. 이긴다면 min에 -1을 넣는다. 왜냐면 내가 이긴다면 상대방은 지니까 -1.
    • 다음 map[i][j]=now로 변경된 map을 가지고 game() 재귀 호출로 상대방 차례로 넘어간다. game()의 리턴값 역시 min에 넣는다.
    • map[i][j]=0은 (i, j)에 now가 놓지 않는 경우이다.
    • for문을 다 돌고 나면 now의 승패를 리턴한다. min을 보고 나의 승패를 넘긴다.
    • min은 -1, 0, 1 값을 가지며 여기에 따라 now의 승패를 넘기면 된다.
    • 또한, min은 2로 초기화했기 때문에 map에 0이 없다면 갱신이 일어나지 않는다. 이 경우는 map이 꽉찼고, 현재 재귀 호출 전에서 isWin()으로 이긴 사람이 없다는 뜻이므로 무승부인 0을 리턴한다.
  3. isWin()으로 map에서 now가 이기는지 확인한다
    • 첫 번째 for문은 세로로 빙고를 만든 경우이다.
    • 두 번째는 가로.
    • 첫 번째 if문은 우측하단 방향으로 대각선, 다음 if문은 우측상단으로 대각선이다.
    • 총 4가지의 경우 중 하나라도 만족하면 true를 리턴한다.

👏 해결 완료!