题目重点:玩家轮流落不同单字符,任意方向连续三枚即胜。
题意与假设
这道练习题保留了面经中值得训练的核心约束,并将需要澄清的条件单独列出。现场拿到变体时,先和面试官确认这些条件。
- N>0,players为非空唯一单字符列表,轮流按列表顺序;棋盘满且无人三连为tie。
- 目标三连而不是整行N枚;game结束后拒绝继续落子。N<3不会产生三连,只可能tie。
解题路线
- 保存稀疏board映射。play先检查状态、轮次、范围与占位,然后落当前player。
- 新出现胜利必须经过新子,所以仅沿竖/横/两对角轴分别向两侧最多查两步,累计同player连续长度≥3即胜。
- 若没有胜且满盘则tie,否则切下个turn。getStatus读状态。若目标改为整行,可每player维护行/列/两对角计数,但不能用全行累计当三连:中间空隙仍无胜。
手动走一遍
5×5中A在(1,1),(1,2),(1,3)连续三枚便胜,无需一整行5枚。同玩家(1,0),(1,2),(1,4)三枚但不连续,无胜;occupied位置重放应拒绝,不多计步。
Python 参考实现
下面的代码按上文假设实现,适合本地运行并追加自己的测试用例。
代码区可横向滚动;使用键盘时,先聚焦代码区再按方向键查看。
class TicTacToe:
def __init__(self,n,players):
if n<=0 or not players or any(type(p) is not str or len(p)!=1 for p in players) or len(players)!=len(set(players)):raise ValueError("positive n and unique players required")
self.n=n;self.players=list(players);self.turn=0;self.board={};self.status='PLAYING'
def getStatus(self):return self.status
def play(self,player,r,c):
if self.status!='PLAYING' or player!=self.players[self.turn] or not 0<=r<self.n or not 0<=c<self.n or (r,c) in self.board:raise ValueError("illegal move")
self.board[r,c]=player
for dr,dc in ((1,0),(0,1),(1,1),(1,-1)):
length=1
for sign in (-1,1):
for step in range(1,3):
if self.board.get((r+sign*step*dr,c+sign*step*dc))==player:length+=1
else:break
if length>=3:self.status=('WIN',player);return self.status
if len(self.board)==self.n*self.n:self.status='TIE'
else:self.turn=(self.turn+1)%len(self.players)
return self.status
复杂度
三连常数方向/步数,play期望O(1),getStatus O(1),棋盘O(已下步数)最多O(N²)。若连L目标,直接扫描O(L)。
易错点与追问
容易失分的边界
- 忽略轮次会让同一玩家连续落子。
- board.get不存在为None,玩家身份必须不与缺失哨兵混淆。
继续深挖
- 全行胜且K玩家如何保持每玩家计数与空间上界?
- 撤销上一步时如何恢复turn、status及计数?
