틱택토는 두 사람이 하는 게임으로 처음에 3x3의 빈칸으로 이루어진 게임판에 선공이 "O", 후공이 "X"를 번갈아가면서 빈칸에 표시하는 게임입니다. 가로, 세로, 대각선으로 3개가 같은 표시가 만들어지면 같은 표시를 만든 사람이 승리하고 게임이 종료되며 9칸이 모두 차서 더 이상 표시를 할 수 없는 경우에는 무승부로 게임이 종료됩니다.
할 일이 없어 한가한 머쓱이는 두 사람이 하는 게임인 틱택토를 다음과 같이 혼자서 하려고 합니다.
틱택토는 단순한 규칙으로 게임이 금방 끝나기에 머쓱이는 한 게임이 종료되면 다시 3x3 빈칸을 그린 뒤 다시 게임을 반복했습니다. 그렇게 틱택토 수 십 판을 했더니 머쓱이는 게임 도중에 다음과 같이 규칙을 어기는 실수를 했을 수도 있습니다.
게임 도중 게임판을 본 어느 순간 머쓱이는 본인이 실수를 했는지 의문이 생겼습니다. 혼자서 틱택토를 했기에 게임하는 과정을 지켜본 사람이 없어 이를 알 수는 없습니다. 그러나 게임판만 봤을 때 실제로 틱택토 규칙을 지켜서 진행했을 때 나올 수 있는 상황인지는 판단할 수 있을 것 같고 문제가 없다면 게임을 이어서 하려고 합니다.
머쓱이가 혼자서 게임을 진행하다 의문이 생긴 틱택토 게임판의 정보를 담고 있는 문자열 배열 board가 매개변수로 주어질 때, 이 게임판이 규칙을 지켜서 틱택토를 진행했을 때 나올 수 있는 게임 상황이면 1을 아니라면 0을 return 하는 solution 함수를 작성해 주세요.
board의 길이 = board[i]의 길이 = 3
board의 원소는 모두 "O", "X", "."으로만 이루어져 있습니다.board[i][j]는 i + 1행 j + 1열에 해당하는 칸의 상태를 나타냅니다.
| board | result |
|---|---|
| ["O.X", ".O.", "..X"] | 1 |
| ["OOO", "...", "XXX"] | 0 |
| ["...", ".X.", "..."] | 0 |
| ["...", "...", "..."] | 1 |
입출력 예 #1
예제 1번의 게임판은 다음과 같습니다.
O.X
.O.
..X
선공 후공이 번갈아가면서 다음과 같이 놓았을 때 이러한 게임판이 나올 수 있습니다.
물론 위와 다르게 머쓱이가 2행 2열에 O, 3행 3열에 X, 1행 3열에 X, 1행 1열에 O 순서로 표시를 해서 실수를 했을 가능성도 있지만 "실수를 했을 가능성이 있는가"를 묻는 게 아닌 "이 게임판이 규칙을 지켜서 진행한 틱택토에서 나올 수 있는 상황인가"를 묻는 문제라는 것에 유의해주세요. 따라서 1을 return 합니다.
입출력 예 #2
예제 2번의 게임판은 다음과 같습니다.
OOO
...
XXX
규칙을 지켜서 진행한 틱택토라면 선공과 후공이 번갈아가면서 각각 1행, 3행 중 두 칸씩에 표시를 한 뒤 5번째 차례에 선공이 1행에 가로로 3개의 O를 완성했을 때 종료되므로 적어도 머쓱이가 게임이 종료된 후에도 계속 진행하는 실수를 했다는 것을 추론해 볼 수 있고, 정상적인 틱택토에서는 이러한 상황이 나올 수 없습니다. 따라서 0을 return 합니다.
입출력 예 #3
X가 표시가 되어있습니다. 선공 O 표시가 없이 X만 있으므로 머쓱이가 O를 표시해야 할 때 X를 표시하는 실수를 했다는 것을 추론해 볼 수 있고, 규칙을 지켜서 진행했을 때는 이러한 상황이 나올 수 없습니다. 따라서 0을 return 합니다.입출력 예 #4
class Solution {
char[][] map;
// 승리하는 경우의 수를 구하는 메서드
public int win(char c) {
// 이길 수 있는 경우의 수를 저장할 변수
int winCount = 0;
// 가로 또는 세로가 채워져서 이긴 경우
for(int i = 0; i < 3; i++) {
// 하나의 행이 채워져서 이긴 경우
if(map[i][0] == c && map[i][0] == map[i][1] && map[i][1] == map[i][2]) {
winCount++;
}
// 하나의 열이 채워져서 이긴 경우
if(map[0][i] == c && map[0][i] == map[1][i] && map[1][i] == map[2][i]) {
winCount++;
}
}
// 대각선이 채워져서 이긴 경우
if(map[0][0] == c && map[0][0] == map[1][1] && map[1][1] == map[2][2]) {
winCount++;
}
if(map[2][0] == c && map[2][0] == map[1][1] && map[1][1] == map[0][2]) {
winCount++;
}
return winCount;
}
public int solution(String[] board) {
map = new char[3][3];
// 'O'의 개수, 'X'의 개수
int oCount = 0, xCount = 0;
for(int i = 0; i < 3; i++) {
for(int j = 0; j < 3; j++) {
// map에 정보를 저장
map[i][j] = board[i].charAt(j);
// 'O'의 개수를 카운트
if(map[i][j] == 'O') {
oCount++;
}
// 'X"의 개수를 카운트
else if(map[i][j] == 'X') {
xCount++;
}
}
}
// 'X'의 개수가 'O'의 개수보다 많거나
// 'O'의 개수가 'X'의 개수보다 2개 이상 많을 경우
if(xCount > oCount || (oCount - xCount) > 1) {
return 0;
}
// 'O'가 이기는 경우의 수와 'X'가 이기는 경우의 수가 동시에 존재할 때
if(win('O') > 0 && win('X') > 0) {
return 0;
}
// 'O'가 이기는 경우의 수가 존재할 때
if(win('O') > 0) {
// 'O'의 개수가 'X'의 개수보다 1개 많을 경우 참
return oCount - xCount == 1 ? 1 : 0;
}
// 'X'가 이기는 경우의 수가 존재할 때
if(win('X') > 0) {
// 'X'의 개수가 'O'의 개수와 동일할 경우 참
return oCount - xCount == 0 ? 1 : 0;
}
return 1;
}
}
단순구현하여 진행하였다.
win 메서드는 매개변수 c가 이긴 경우의 수를 반환해주는 메서드이다. 이길 수 있는 경우의 수를 저장할 변수 winCount를 생성하고 가로 또는 세로가 모두 채워져서 이긴 경우 혹은 대각선이 채워져서 이긴 경우를 모두 확인해서 경우의 수를 구해준다.
solution 메서드에서는 map에 정보를 저장하고 'O'의 개수와 'X'의 개수를 구해준다.
여러 조건으로 나누어서 문제를 푸는데 'X'의 개수가 'O'의 개수보다 많거나 'O'의 개수가 'X'의 개수보다 2개 이상 많을 경우 잘못 놓여진 틱택토이므로 0을 반환해준다.
'O'가 이기는 경우의 수가 'X'가 이기는 경우의 수는 둘 중 하나만 존재해야한다. 두 경우가 동시에 존재하는 경우 게임이 이겨도 진행이 됐다는 뜻이기 때문이다. 따라서 0을 반환한다.
'O'가 이기는 경우의 수가 존재할 때 'O'의 개수는 'X'의 개수보다 하나 많아야한다. 이때 1을 반환하고 그 외의 모든 경우는 잘못된 경우이므로 0을 반환한다.
'X'가 이기는 경우의 수가 존재할 때 'O'의 개수는 'X'의 개수와 동일해야한다. 이때 1을 반환하고 그 외의 모든 경우는 잘못된 경우이므로 0을 반환한다.
위의 모든 조건에 만족하지 않는 경우는 아직 누구도 이기지 않은 정상적으로 게임을 진행하고 있는 경우이므로 1을 반환해주면 문제를 해결할 수 있다!
되게 복잡한 탐색 문제로 생각하고 겁을 먹었으나 경우의 수를 잘 구해주면 풀 수 있는 간단한 구현문제였다. 다만 실수를 하는 조건에 대해서 잘 생각했어야했다. 조건이 많다보니 단순하게 if-else 문으로 구현을 하다보면 잘못 포함이 되는 조건들이 많이 생겼다. 이 오류를 찾느라 시간이 좀 걸렸다;; 조건을 잘 생각해서 문제를 풀어야겠다..