美文网首页我的Python自学之路
Python玩转算法—扫雷

Python玩转算法—扫雷

作者: 呆萌狗和求疵喵 | 来源:发表于2017-05-05 22:51 被阅读1235次

    此题来自LeetCode上的一道难度为Medium的题,说是有一张玩到一半的扫雷地图,接下来给你指定一个点击位置,让你预测点击之后,地图将发生怎么样的变化。看到这道题,瞬间让我想起了以前玩扫雷的日子,可惜Mac上没有自带扫雷,与是我又去AppStore上下载了扫雷,重新把玩了一番,经典游戏就是这样,百玩不厌。

    经典扫雷游戏

    题目中,我们用'E'代表未探索的,而且没有雷的点, 用'M'代表有雷的位置, 用'B'代表探索过,而且周围8个邻居点都没有雷的位置,如果某个位置已经探索过,但是周围有雷,用数字代表周围的雷的个数。

    游戏的规则很简单,当我们点击一个未探索的位置时,如果

    1. 当前位置为M, 则扫到雷,将其置为X, 游戏结束
    2. 当前位置E,则将其置为B,然后继续递归处理其周围未探索的位置
    3. 当前位置为E,而且周围有雷,则用周围雷的个数替换
    4. 如果不能探索更多的位置,则返回

    比如
    Input:
    [['E', 'E', 'E', 'E', 'E'],
    ['E', 'E', 'M', 'E', 'E'],
    ['E', 'E', 'E', 'E', 'E'],
    ['E', 'E', 'E', 'E', 'E']]

    Click : [3,0]

    Output:

    [['B', '1', 'E', '1', 'B'],
    ['B', '1', 'M', '1', 'B'],
    ['B', '1', '1', '1', 'B'],
    ['B', 'B', 'B', 'B', 'B']]

    Explanation:

    因为扫雷的地图本身就是一张图,我们很容易想到能不能用图的遍历算法去解决问题,我们可以先试试深度优先遍历DFS

            def updateBoard(self, board, click):
        
            M, N = len(board), len(board[0])
            dirs = [[0, 1], [0, -1], [1, 0], [-1, 0], [-1, -1], [-1, 1], [1, -1], [1, 1]]
            def isInRange(x, y):
                return 0 <= x < M and 0 <= y < N
            
            def dfs(board, x, y):
                if board[x][y] == 'M':
                    board[x][y] = 'X'
                    return
                # 获取周围雷的个数
                mineCnt = sum(1 if isInRange(x + dir[0], y + dir[1]) and board[x + dir[0]][y + dir[1]] == 'M'
                              else 0 for dir in dirs)
                if mineCnt != 0:
                    board[x][y] = str(mineCnt)
                else:
                    
                    board[x][y] = 'B'
                    # 获取周围所有为探索且没有雷的位置
                    eArr = [(x + dir[0], y + dir[1]) for dir in dirs if isInRange(x + dir[0], y + dir[1])
                            and board[x + dir[0]][y + dir[1]] == 'E']
                    for xx, yy in eArr:
                        #递归处理邻居位置
                        dfs(board, xx, yy)
    
            dfs(board, click[0], click[1])
            return board
    

    一般我们在DFS中需要

    1. 维持一个已访问位置的标记集合,这里我们并不需要,因为只要目标位置不为B就表示我们未访问过
    2. 在递归访问的过程中,我们只有当周围没有雷的时候,才会去递归访问邻居位置,否则视为已经到dfs的叶子,递归返回

    我们也可以使用BFS来做这个遍历处理:

    
        def updateBoard2(self, board, click):
            M, N = len(board), len(board[0])
            def isInRange(x, y):
                return 0 <= x < M and 0 <= y < N
    
            queue = []
            dirs = [[0, 1], [0, -1], [1, 0], [-1, 0], [-1, -1], [-1, 1], [1, -1], [1, 1]]
            queue.append(click)
            while len(queue) > 0:
                x, y = queue.pop(0)
                if board[x][y] == 'M':
                    board[x][y] = 'X'
                    continue
    
                mineCnt = sum(1 if isInRange(x + dir[0], y + dir[1]) and board[x + dir[0]][y + dir[1]] == 'M'
                              else 0 for dir in dirs)
                
                if mineCnt == 0:
                    board[x][y] = 'B'
                    for dir in dirs:
                        xx, yy = x + dir[0], y + dir[1]
                        if isInRange(xx,yy) and board[xx][yy] == 'E':
                            # 这一步非常重要,避免重复加入队列
                            board[xx][yy] = 'B'
                            queue.append((xx, yy))
                            
                else:
                    board[x][y] = str(mineCnt)
            return board
    

    采用一个队列,每次将同一距离的点加入队列,同样只有当前位置的周围没有雷的时候,才会将没有访问过的邻居位置加入队列。

    Python语言越来越发挥重要的作用,尤其是在如今这个AI炙手可热的年代,不管我们在从事什么平台的开发,多学一点python对我们肯定有好处。Python玩转算法这个系列就是围绕python展开,用python去解决一些问题,实现一些算法。如果有读者对python也感兴趣,却找不到合适的资料去学习,不妨跟随这个系列一起学习,欢迎各位读者关注和转发~

    相关文章

      网友评论

        本文标题:Python玩转算法—扫雷

        本文链接:https://www.haomeiwen.com/subject/gchdtxtx.html