黑白翻转棋

标签: 广度优先搜索 数组 矩阵

难度: Medium

在 `n*m` 大小的棋盘中,有黑白两种棋子,黑棋记作字母 `"X"`, 白棋记作字母 `"O"`,空余位置记作 `"."`。当落下的棋子与其他相同颜色的棋子在行、列或对角线完全包围(中间不存在空白位置)另一种颜色的棋子,则可以翻转这些棋子的颜色。 ![1.gif](https://pic.leetcode-cn.com/1630396029-eTgzpN-6da662e67368466a96d203f67bb6e793.gif){:height=170px}![2.gif](https://pic.leetcode-cn.com/1630396240-nMvdcc-8e4261afe9f60e05a4f740694b439b6b.gif){:height=170px}![3.gif](https://pic.leetcode-cn.com/1630396291-kEtzLL-6fcb682daeecb5c3f56eb88b23c81d33.gif){:height=170px} 「力扣挑战赛」黑白翻转棋项目中,将提供给选手一个未形成可翻转棋子的棋盘残局,其状态记作 `chessboard`。若下一步可放置一枚黑棋,请问选手最多能翻转多少枚白棋。 **注意:** - 若翻转白棋成黑棋后,棋盘上仍存在可以翻转的白棋,将可以 **继续** 翻转白棋 - 输入数据保证初始棋盘状态无可以翻转的棋子且存在空余位置 **示例 1:** > 输入:`chessboard = ["....X.","....X.","XOOO..","......","......"]` > > 输出:`3` > > 解释: > 可以选择下在 `[2,4]` 处,能够翻转白方三枚棋子。 **示例 2:** > 输入:`chessboard = [".X.",".O.","XO."]` > > 输出:`2` > > 解释: > 可以选择下在 `[2,2]` 处,能够翻转白方两枚棋子。 ![2126c1d21b1b9a9924c639d449cc6e65.gif](https://pic.leetcode-cn.com/1626683255-OBtBud-2126c1d21b1b9a9924c639d449cc6e65.gif) **示例 3:** > 输入:`chessboard = [".......",".......",".......","X......",".O.....","..O....","....OOX"]` > > 输出:`4` > > 解释: > 可以选择下在 `[6,3]` 处,能够翻转白方四枚棋子。 ![803f2f04098b6174397d6c696f54d709.gif](https://pic.leetcode-cn.com/1630393770-Puyked-803f2f04098b6174397d6c696f54d709.gif) **提示:** - `1 <= chessboard.length, chessboard[i].length <= 8` - `chessboard[i]` 仅包含 `"."、"O"` 和 `"X"`

Submission

运行时间: 42 ms

内存: 16.0 MB

class Solution:
    def flipChess(self, board: List[str]) -> int:
        m, n = len(board), len(board[0])
        def search(r,c):
            que = [(r,c)]
            grid = [list(row) for row in board]
            grid[r][c] = 'X'
            count = 0
            
            while len(que)>0:
                r,c = que.pop()
                for x_step, y_step in directions:
                    x, y = r+x_step, c+y_step
                    while 0<=x<m and 0<=y<n and grid[x][y] == 'O':
                        x, y = x + x_step, y + y_step
                    if 0<=x<m and 0<=y<n and grid[x][y] == 'X':
                        x, y = x - x_step,  y - y_step
                    
                        count += max(abs(x-r), abs(y-c))
                        while x!=r or y!=c:
                            grid[x][y] = 'X'
                            que.append((x,y))
                            x, y = x - x_step, y - y_step               
            return count
        directions = [(x_step, y_step) for x_step in range(-1,2) for y_step in range(-1,2) if x_step!=0 or y_step!=0]
        return max(search(r,c) for r in range(m) for c in range(n) if board[r][c] == '.')

Explain

这个题解的思路是通过模拟下棋的过程来寻找最优的下棋位置。首先,遍历棋盘上的每一个空位('.'),尝试在这个位置放置黑棋('X'),然后从这个位置向八个方向(上、下、左、右、四个对角线方向)探索,查看是否可以通过放置这个棋子来翻转周围的白棋('O')。如果在某个方向上,从当前位置出发,连续遇到白棋后再遇到一个黑棋,那么这一路上的所有白棋都可以被翻转成黑棋。每次模拟下棋后,计算可以翻转的白棋数量,并更新最大值。最后返回最大的翻转数量。

时间复杂度: O(n^4)

空间复杂度: O(n^2)

class Solution:
    def flipChess(self, board: List[str]) -> int:
        m, n = len(board), len(board[0])
        def search(r,c):
            que = [(r,c)] # 用于存储需要检查的棋子位置
            grid = [list(row) for row in board] # 创建棋盘的副本
            grid[r][c] = 'X' # 在指定位置放置黑棋
            count = 0
            while len(que)>0:
                r,c = que.pop()
                for x_step, y_step in directions:
                    x, y = r+x_step, c+y_step
                    while 0<=x<m and 0<=y<n and grid[x][y] == 'O':
                        x, y = x + x_step, y + y_step
                    if 0<=x<m and 0<=y<n and grid[x][y] == 'X':
                        x, y = x - x_step,  y - y_step
                        count += max(abs(x-r), abs(y-c))
                        while x!=r or y!=c:
                            grid[x][y] = 'X'
                            que.append((x,y))
                            x, y = x - x_step, y - y_step
            return count
        directions = [(x_step, y_step) for x_step in range(-1,2) for y_step in range(-1,2) if x_step!=0 or y_step!=0]
        return max(search(r,c) for r in range(m) for c in range(n) if board[r][c] == '.')

Explore

这种模拟方法的效率会随着棋盘大小的增加而显著降低。算法的时间复杂度主要由两部分组成:首先是遍历每一个空位尝试放置棋子,其次是从每个空位向八个方向进行搜索。如果棋盘的维度是m×n,那么空位的数量最多是m×n个,而每个空位都可能需要进行八个方向的深度搜索,每个方向的搜索长度最多为棋盘的最大维度(max(m,n))。因此,总的时间复杂度大约是O(m*n*8*max(m,n)),即O(m^2*n^2)至O(m*n^3),这是一个多项式级增长的时间复杂度。对于大型棋盘,这种算法可能变得非常缓慢,特别是在空间复杂度也随之增大的情况下。

这种选择确保了翻转的合法性,因为根据黑白翻转棋(通常称为奥赛罗或Reversi)的规则,一个有效的落子必须夹住对手的一个或多个棋子(在这里是白棋),并且落子的两端必须是相同颜色的棋子。在算法中,从放置的黑棋开始向某个方向搜索,如果连续遇到白棋并最终遇到黑棋,这意味着这一系列的白棋被两个黑棋夹住,因此可以合法地翻转这些白棋。如果搜索到的是边界或者空位,则不满足翻转条件。这种模拟确实反映了游戏的实际规则。

队列在这里主要用于存储和追踪在翻转过程中可能需要进一步考虑的棋子位置。这种方法类似于宽度优先搜索(BFS),队列用来确保每次处理的是先发现的棋子位置。使用队列可以帮助算法系统地检查所有需要翻转的棋子,并逐一更新棋盘状态。尽管队列是有效的,但在某些情况下可以考虑使用栈来代替队列,这将改变搜索的顺序为深度优先搜索(DFS)。DFS可能在某些方向的搜索中更快地达到边界条件,从而减少部分计算。然而,这种替换可能对算法的总体性能影响不大,因为主要的时间消耗在于遍历和检查棋盘的每个位置。