难度: Hard
Submission
运行时间: 200 ms
内存: 19.6 MB
from sortedcontainers import SortedList class Solution: def gobang(self, pieces: List[List[int]]) -> str: B = [defaultdict(SortedList) for _ in range(4)] # 黑棋的四类直线:水平, 垂直, 斜率为1, 斜率为-1 W = [defaultdict(SortedList) for _ in range(4)] # 白棋的四类直线:水平, 垂直, 斜率为1, 斜率为-1 def add_piece(x, y, c): P = W if c else B P[0][y].add(x) P[1][x].add(y) P[2][y - x].add(x) P[3][y + x].add(x) def get_pos(k, v, d): if d == 0: x, y = v, k elif d == 1: x, y = k, v elif d == 2: x, y = v, k + v elif d == 3: x, y = v, k - v return (x, y) # 找到P的棋子中,冲np(3或4)的点(填上这个点就必胜,并且没有Q的棋子阻挡) def find_win_ps(P, Q, np) -> defaultdict(set): win_points = defaultdict(set) for d in range(4): for k in P[d]: ps, n = P[d][k], len(P[d][k]) if n < np: continue for i in range(n + 1 - np): dif = ps[i + np - 1] - ps[i] if dif < 5: # <5 说明能成5 # 找出空缺的v。找规律发现在[ps[i]-(4-dif), ps[i] + 5],不是已有的v的点 vs = [v for v in range(ps[i] + dif - 4, ps[i] + 5) if v not in ps[i:i + np] and not has_piece(Q, (k, v, d))] nvs = len(vs) if nvs < 5 - np: continue # 3->2, 4->1 dt = 4 - np for j in range(nvs - dt): v1, v2 = vs[j], vs[j + dt] if v2 - v1 > 4: continue xy1, xy2 = get_pos(k, v1, d), get_pos(k, v2, d) win_points[xy1].add(xy2) win_points[xy2].add(xy1) if np == 4 and len(win_points) > 1: return win_points return win_points def has_piece(X, kvd) -> bool: k, v, d = kvd return k in X[d] and v in X[d][k] # 开始,棋子先存好 for x, y, c in pieces: add_piece(x, y, c) # 1. 黑先手有4连黑子,并且有空位放下能成5连黑子,就黑胜 if len(find_win_ps(B, W, 4)) > 0: return 'Black' # 白如果有多个冲4就赢 live_w4 = find_win_ps(W, B, 4) if len(live_w4) > 1: return 'White' # 白只有1个冲4,黑先堵上;如果黑有多个冲4就赢,否则None if len(live_w4) == 1: x, y = list(live_w4.keys())[0] add_piece(x, y, 0) return 'Black' if len(find_win_ps(B, W, 4)) > 1 else 'None' # 检查最开始如果有形成双4的点,就黑赢 return 'Black' if any(len(ds) > 1 for ds in find_win_ps(B, W, 3).values()) else 'None'
Explain
本题解采用了搜索和评估的策略来预测五子棋的输赢情况。核心思路是通过哈希表记录各类直线(水平、垂直、两个对角线)上的棋子位置。通过这种数据结构可以快速地评估和更新棋局。具体方法包括:1. 初始化黑白棋的数据结构来记录棋子位置;2. 为每种颜色的棋子定义添加函数,便于更新棋局;3. 评估每种直线上的棋子配置,查找可以直接赢得比赛的落子点;4. 根据当前棋局使用最优策略预测接下来的三步棋,包括黑白双方的最优防守和进攻。根据棋局动态评估,判断棋局的输赢情况。
时间复杂度: O(m * n * log n)
空间复杂度: O(n)
from sortedcontainers import SortedList from collections import defaultdict class Solution: def gobang(self, pieces: List[List[int]]) -> str: B = [defaultdict(SortedList) for _ in range(4)] # Black pieces lines: horizontal, vertical, diagonal with slope 1 and -1 W = [defaultdict(SortedList) for _ in range(4)] # White pieces lines def add_piece(x, y, c): P = W if c else B # Select color array P[0][y].add(x) # Add to horizontal line P[1][x].add(y) # Add to vertical line P[2][y - x].add(x) # Add to diagonal line slope 1 P[3][y + x].add(x) # Add to diagonal line slope -1 def get_pos(k, v, d): # Helper to get the actual position based on line and index if d == 0: x, y = v, k elif d == 1: x, y = k, v elif d == 2: x, y = v, k + v elif d == 3: x, y = v, k - v return (x, y) # Search for winning moves, where adding a piece leads to a victory def find_win_ps(P, Q, np) -> defaultdict(set): win_points = defaultdict(set) for d in range(4): for k in P[d]: ps, n = P[d][k], len(P[d][k]) if n < np: continue # Not enough pieces to form a line for i in range(n + 1 - np): dif = ps[i + np - 1] - ps[i] if dif < 5: # Potential win by filling gaps vs = [v for v in range(ps[i] + dif - 4, ps[i] + 5) if v not in ps[i:i + np] and not has_piece(Q, (k, v, d))] nvs = len(vs) if nvs < 5 - np: continue # Not enough spots to win dt = 4 - np for j in range(nvs - dt): v1, v2 = vs[j], vs[j + dt] if v2 - v1 > 4: continue # Too far apart to form a line xy1, xy2 = get_pos(k, v1, d), get_pos(k, v2, d) win_points[xy1].add(xy2) win_points[xy2].add(xy1) if np == 4 and len(win_points) > 1: return win_points # Immediate win for 4 in a row return win_points def has_piece(X, kvd) -> bool: # Check if a position is occupied k, v, d = kvd return k in X[d] and v in X[d][k] for x, y, c in pieces: add_piece(x, y, c) # Add initial pieces to the board if len(find_win_ps(B, W, 4)) > 0: return 'Black' # Black wins with a potential 5 in a row live_w4 = find_win_ps(W, B, 4) if len(live_w4) > 1: return 'White' # White wins if multiple 4 in a row options exist if len(live_w4) == 1: x, y = list(live_w4.keys())[0] add_piece(x, y, 0) # Block white's potential 4 in a row return 'Black' if len(find_win_ps(B, W, 4)) > 1 else 'None' # Check if blocking leads to a new win or a draw return 'Black' if any(len(ds) > 1 for ds in find_win_ps(B, W, 3).values()) else 'None' # Check for possible double 4's for black
Explore
在评估棋局时,通过检查棋局的每一行、每一列以及两个对角线方向上的棋子配置来确定是否存在可以立即获胜的落子点。对于对角线(正斜率和负斜率),使用映射来将斜线上的点映射到一维数组,从而使问题转化为一维线性问题。具体来说,对于正斜率对角线,使用`y - x`作为键值来归类所有对应的棋子位置;对于负斜率对角线,使用`y + x`作为键。这样,可以通过查找每个键值下的棋子数组,使用同样的逻辑来评估是否存在连续的五个棋子或是存在空位可以通过落子立即形成五子连线,从而判断胜负。
在寻找赢棋点时,差值`dif`小于5意味着在这个区间内的棋子数目足够密集,可能存在空缺但可以通过填补这些空缺来形成连续的五个棋子,从而获胜。`dif`是指在当前检查的线段(无论是水平、垂直还是对角线)上连续棋子的最远端和最近端的位置差。如果这个差值小于5,意味着这两个棋子之间(包括这两个点自身)的距离最多只有四个位置,因此只要适当地填补中间未被占据的位置(如果有的话),就有可能形成五子连线。
在函数`find_win_ps`中,变量`np`代表当前考虑的棋子连线的长度。这是一个关键参数,用于指定需要检查的连续棋子的数量。例如,在搜索四子连线的情况下,`np`会被设定为4。根据`np`的值,函数将搜索所有可能的线段,看是否存在连续的`np`个棋子,且这些棋子之间的空隙足够通过落子在接下来的动作中形成五子连线。这样的逻辑使得算法可以灵活地根据不同的棋局状态评估接近获胜条件的棋子布局,进而预测和制定相应的进攻或防守策略。