leetcode 794. Valid Tic-Tac-Toe State
题目描述
A Tic-Tac-Toe board is given as a string array board
. Return True if and only if it is possible to reach this board position during the course of a valid tic-tac-toe game.
The board
is a 3 x 3 array, and consists of characters " "
, "X"
, and "O"
. The " "
character represents an empty square.
Here are the rules of Tic-Tac-Toe:
- Players take turns placing characters into empty squares (” “).
- The first player always places “X” characters, while the second player always places “O” characters.
- “X” and “O” characters are always placed into empty squares, never filled ones.
- The game ends when there are 3 of the same (non-empty) character filling any row, column, or diagonal.
- The game also ends if all squares are non-empty.
- No more moves can be played if the game is over.
Note:
board
is a length-3 array of strings, where each stringboard[i]
has length 3.- Each
board[i][j]
is a character in the set{" ", "X", "O"}
.
Difficulty: Medium
794. Valid Tic-Tac-Toe State
中文描述
在一个井字格游戏中,先手持有X的棋子,后手持有O的棋子,两者交替在井字格的空位中交替下子,当任意一方使得斜着,竖着或是横着的三个位置为同一种棋子时,该人获胜,游戏结束。或者双方都没有空格下子时,游戏结束。现在给你一个井字格游戏的当前情况,问你在满足上述条件下,该情况是否成立。
输入格式
输入一个列表board
,board
的每个字符串表示游戏中的一行的情况。
Examples:
Input: board = [“O “, ” “, ” “]
Output: false
解释:
先手是X,所以不可能出现开场为O的情况。Input: board = [“XOX”, ” X “, ” “]
Output: false
解释:
X和O是交替进行,不可能出现下了3次X却只下了1次O。Input: board = [“XXX”, ” “, “OOO”]
Output: false
解释:
因为X先手,在出现”XXX”的时候,比赛就已经结束了,所以不可能再出现”OOO”的情况。Input: board = [“XOX”, “O O”, “XOX”]
Output: true
解释:
显然这种情况是可以做到的。
解答思路
解法一:dfs
1.我们根据目标状态,一步一步下棋。考虑用dfs遍历的方式遍历所有不违反规则的步骤,看是否存在一种下法能够到达目标状态。
2.我们先把目标状态把所有X,O的位置记录下来,之后每次下棋都从对应棋子的位置中选择一步。
3.每下完一步判断是否达到目标状态,是的话就返回true,否则判断该局游戏是否已经结束,如果是的话则返回上一步,重新选择下棋的位置。直到无棋可下的情况,说明该目标状态无解,返回false。
4.详细的时间复杂度不好估计,与X和O的棋子个数相关。估计了下最差情况应该是 n! n ! ,这边 n n <script type="math/tex" id="MathJax-Element-4593">n</script>不会大于9,所以算是常数时间。
解法二:数学分析
1.其实可以不用像上述方法遍历所有情况,可以通过数学分析来判断该局游戏是否成立。
2.首先由于X先手,则X的棋子个数肯定不会小于O棋子的个数,如果违反这点该局游戏不会成立,如案例2.
3.并且根据场上每个棋子的个数,我们也能判断最后一手是下X还是O。如案例三情况。在比如[“X X”,”OXX”,”OOO”]这样的情况。如果下三个O先完成,则游戏应该已经结束。但是,由于O棋摆放在3个的时候没有必然获胜,则X棋存在某种情况可以继续下下去,即存在[“X X”,”O X”,” OO”]–>[“X X”,”OXX”,” OO”]–>[“X X”,”OXX”,”OOO”]。所以只要前一个玩家没有必然获胜,下一个玩家就还能继续。如果在下X前O已经必然获胜,则该局游戏不成立(对O也是同样道理)。
4.时间复杂度估计,只要统计X和O的个数和判断是否提前结束,所以是常数时间内。
代码
解法一,dfs,
class Solution(object):
def validTicTacToe(self, board):
"""
:type board: List[str]
:rtype: bool
145ms
"""
def is_win(mark, board):
left2right = ''
right2left = ''
target = mark * 3
for i in range(3):
# 一行都是
if ''.join(board[i]) == target:
return True
# 一列都是
s = board[0][i] + board[1][i] + board[2][i]
if s == target:
return True
# 从左到右对角线
left2right += board[i][i]
# 从右到左对角线
right2left += board[-1 - i][i]
if left2right == target or right2left == target:
return True
return False
dicts = {'X':[], 'O':[], ' ':[]}
for i in range(3):
for j in range(3):
dicts[board[i][j]].append((i, j))
new_board = [[' ' for _ in range(3)] for _ in range(3)]
def dfs(count, dicts, new_board):
flag = True
# 判断是否已经到达目标状态
for i in range(3):
if ''.join(new_board[i]) != board[i]:
flag = False
break
if flag:
return True
# 当前下的棋子
if count % 2 == 0:
mark = 'X'
else:
mark = 'O'
before_mark = 'X' if mark == 'O' else 'O'
# 判断上一个玩家是不是已经赢得比赛
if is_win(before_mark, new_board):
return False
for i in dicts[mark]:
new_board[i[0]][i[1]] = mark
copy_dicts = {}
copy_dicts['X'] = dicts['X'][:]
copy_dicts['O'] = dicts['O'][:]
copy_dicts[mark].remove(i)
if dfs(count + 1, copy_dicts, new_board):
return True
new_board[i[0]][i[1]] = ''
return False
return dfs(0, dicts, new_board)
解法二,数学分析:
class Solution(object):
def validTicTacToe(self, board):
"""
:type board: List[str]
:rtype: bool
34ms
"""
# 判断是否已经获胜
def is_win(mark, board):
left2right = ''
right2left = ''
target = mark * 3
for i in range(3):
# 一行都是
if board[i] == target:
return True
# 一列都是
s = board[0][i] + board[1][i] + board[2][i]
if s == target:
return True
# 从左到右对角线
left2right += board[i][i]
# 从右到左对角线
right2left += board[-1 - i][i]
if left2right == target or right2left == target:
return True
return False
count_X = 0
count_O = 0
# 统计X,O各自的个数
for s in board:
for char in s:
if char == 'X':
count_X += 1
elif char == 'O':
count_O += 1
# 判断OX个数是否一致,一致的话需要判断X之前是不是已经赢了
if count_O == count_X:
mark = 'X'
# 判断OX个数是否一致,X比O大一的话需要判断O之前是不是已经赢了
elif count_X == count_O + 1:
mark = 'O'
# 否则两者差了2个以上,肯定错误
else:
return False
# 这种情况一定能够成立
if count_O < 3:
return True
return not is_win(mark, board)