题目地址:
https://leetcode.com/problems/valid-tic-tac-toe-state/
给定一个方阵,两个人玩游戏,每个人可以在某个方格填上带有自己标记的记号,玩家 1 1 1标记的是 X X X,玩家 2 2 2标记的是 O O O。如果有某一行或者某一列或者某个对角线(指的是最长的两条对角线)全标记了相同的记号,则判定那个玩家获胜。开局玩家 1 1 1先走,然后两个玩家轮流走。如果某个玩家获胜则游戏立即停止。
现在给定一个盘面,问该盘面是否合法。合法的意思是存在两个玩家的走法,使得某个时刻盘面刚好等于上面给定的盘面。
思路是先考虑必要条件,然后尝试证明它们也是充分条件。容易知道的必要条件有:
1、合法盘面必然
X
X
X的个数
x
x
x与
O
O
O的个数
o
o
o满足
x
=
o
x=o
x=o或者
x
=
o
+
1
x=o+1
x=o+1,因为
X
X
X先走,所以
X
X
X要么和
O
O
O一样多,要么只多一个;
2、如果
x
=
o
x=o
x=o,那么
X
X
X不能赢,因为最后一步是
O
O
O走,如果
X
X
X赢了那应该游戏停止,不应该还能继续;
3、如果
x
=
o
+
1
x=o+1
x=o+1,那么
O
O
O不能赢,理由同上。
下面证明上面的三个条件合起来也是充分条件,即如果某个条件同时满足上面三个条件,那么这个盘面必然合法。
1、
x
=
o
x=o
x=o:如果
O
O
O没赢,那么随便走;如果
O
O
O赢了,那么将其赢了的那个行或者列或者对角线其中一个
O
O
O设定为
O
O
O的最后一步,别的步数随便走;
2、
x
=
o
+
1
x=o+1
x=o+1:如果
X
X
X没赢,那么随便走;如果
X
X
X赢了,那么将其赢了的那个行或者列或者对角线其中一个
X
X
X设定为
X
X
X的最后一步,别的步数随便走;
综上所述上面三个条件是充要条件。
具体实现上,可以将两个玩家分别映射到 − 1 -1 −1和 1 1 1上,然后用一个数组标记每一行、列、对角线的数字和,每次一个玩家走的时候,就将格子对应的行、列、对角线上面的数字加上对应的 − 1 -1 −1或者 1 1 1。这样只要发现某个数字和为 n n n或者 − n -n −n就知道某个玩家赢了。代码如下:
public class Solution {
public boolean validTicTacToe(String[] board) {
if (board == null || board.length == 0 || board[0].length() == 0) {
return true;
}
int n = board.length;
// 记录每一行、列、对角线对应的数字和
int[] rows = new int[n], cols = new int[n];
int diag = 0, antidiag = 0;
// 记录盘面里一共有多少个O和X
int ocount = 0, xcount = 0;
// 记录盘面里O是否赢了,X是否赢了
boolean owin = false, xwin = false;
for (int i = 0; i < board.length; i++) {
for (int j = 0; j < board[i].length(); j++) {
char ch = board[i].charAt(j);
rows[i] += mapping(ch);
cols[j] += mapping(ch);
if (i == j) {
diag += mapping(ch);
}
if (i + j == n - 1) {
antidiag += mapping(ch);
}
if (ch == 'X') {
xcount++;
} else if (ch == 'O') {
ocount++;
}
if (rows[i] == n || cols[j] == n || diag == n || antidiag == n) {
owin = true;
}
if (rows[i] == -n || cols[j] == -n || diag == -n || antidiag == -n) {
xwin = true;
}
}
}
// 分别判断一下三个条件
if (xcount < ocount || xcount > ocount + 1) {
return false;
}
if (xcount == ocount && xwin) {
return false;
}
if (xcount == ocount && owin) {
return false;
}
return true;
}
// 将X映射为-1,O映射为O,别的映射为0
private int mapping(char c) {
if (c == 'X') {
return -1;
} else if (c == 'O') {
return 1;
} else {
return 0;
}
}
}
时间复杂度 O ( n 2 ) O(n^2) O(n2),空间 O ( n ) O(n) O(n), n n n为盘面边长。