LeetCode算法题5:DFS/BFS-扫雷游戏


前言

      DFS 和 BFS,可参考之前的一篇文章:https://blog.csdn.net/Little_ant_/article/details/123415889?spm=1001.2014.3001.5501 介绍了二者的伪代码实现和关于 BFS 实现时需要注意的地方。

      扫雷游戏和被围绕的区域均为不太简单的DFS/BFS,即当前元素状态依赖于下一步(周围)的元素值。

一、扫雷游戏

      题目链接:https://leetcode-cn.com/problems/minesweeper/

      题目描述:
让我们一起来玩扫雷游戏!

给你一个大小为 m x n 二维字符矩阵 board ,表示扫雷游戏的盘面,其中:

‘M’ 代表一个 未挖出的 地雷,
‘E’ 代表一个 未挖出的 空方块,
‘B’ 代表没有相邻(上,下,左,右,和所有4个对角线)地雷的 已挖出的 空白方块,
数字(‘1’ 到 ‘8’)表示有多少地雷与这块 已挖出的 方块相邻,
‘X’ 则表示一个 已挖出的 地雷。
给你一个整数数组 click ,其中 click = [clickr, clickc] 表示在所有 未挖出的 方块(‘M’ 或者 ‘E’)中的下一个点击位置(clickr 是行下标,clickc 是列下标)。

根据以下规则,返回相应位置被点击后对应的盘面:

如果一个地雷(‘M’)被挖出,游戏就结束了- 把它改为 ‘X’ 。
如果一个 没有相邻地雷 的空方块(‘E’)被挖出,修改它为(‘B’),并且所有和其相邻的 未挖出 方块都应该被递归地揭露。
如果一个 至少与一个地雷相邻 的空方块(‘E’)被挖出,修改它为数字(‘1’ 到 ‘8’ ),表示相邻地雷的数量。
如果在此次点击中,若无更多方块可被揭露,则返回盘面。

DFS :

      思路为:1,如果当前位置元素为’M’,修改其为’X‘并返回;如果为’E’的话,**这里注意和简单的 DFS 有点差异,区别在于简单的 DFS 这时便可以对当前位置元素做访问或修改,但是本题中需要首先判断当前位置周围 8 个元素的状态,然后根据结果来对当前位置进行修改。**2,根据题意,DFS 递归的情况仅针对于当前位置为’B’的情况,相对的是如果当前位置为数字的话,不需要进行递归了。参考代码如下:

    int[] xi={1,0,0,-1,1,1,-1,-1};
    int[] yi={0,1,-1,0,-1,1,-1,1};
    public char[][] updateBoard(char[][] board, int[] click) {
        int x=click[0],y=click[1];
        if(board[x][y]=='M'){
            board[x][y]='X';
            return board;
        }
        solve(board,x,y);
        return board;
    }
    public void solve(char[][] board,int x,int y){
        //和一般 DFS 不一样的是:当前位置的判断依赖于其周围位置元素的状态
        int count=0;
        for(int i=0;i<8;i++){
            int newX=x+xi[i],newY=y+yi[i];
            if(newX<0||newY<0||newX>=board.length||newY>=board[0].length)
                continue;
            if(board[newX][newY]=='M')
                count++;
        }
        if(count!=0)
            board[x][y]=(char)('0'+count);
        else{
            board[x][y]='B';
            for(int i=0;i<8;i++){
                int newX=x+xi[i],newY=y+yi[i];
                if(newX<0||newY<0||newX>=board.length||newY>=board[0].length)
                    continue;
                if(board[newX][newY]=='E')//这里注意仅对未翻开的格子进行递归判断。
                    solve(board,newX,newY);
            }
        }
    } 

BFS

       BFS 中,为了避免相同元素重复入队有两种解决方法:1,在入队时修改其值,2,采用标记数组。本题没法在入队时修改其值,所以采用标记数组的方法(在入队时标记)。此问题在博客https://blog.csdn.net/Little_ant_/article/details/123415889?spm=1001.2014.3001.5501有提到过。参考代码如下:

public char[][] updateBoard(char[][] board, int[] click) {
        int[] xi={1,0,0,-1,1,1,-1,-1};
        int[] yi={0,1,-1,0,-1,1,-1,1};
        int x=click[0],y=click[1];
        LinkedList<int[]> queue=new LinkedList<>();
        queue.add(new int[]{x,y});
        if(board[x][y]=='M'){
            queue.remove();
            board[x][y]='X';
        }
        int count;
        boolean[][] flag=new boolean[board.length][board[0].length];
        flag[x][y]=true;
        while(!queue.isEmpty()){
            int[] tmp=queue.poll();
            x=tmp[0];
            y=tmp[1];
            count=0;
            for(int i=0;i<8;i++){
                int newX=x+xi[i],newY=y+yi[i];
                if(newX<0||newY<0||newX>=board.length||newY>=board[0].length)
                    continue;
                if(board[newX][newY]=='M')
                    count++;
            }
            if(count!=0)
                board[x][y]=(char)('0'+count);
            else{
                board[x][y]='B';
                for(int i=0;i<8;i++){
                    int newX=x+xi[i],newY=y+yi[i];
                    if(newX<0||newY<0||newX>=board.length||newY>=board[0].length||flag[newX][newY])
                        continue;
                    if(board[newX][newY]=='E'){
                        queue.add(new int[]{newX,newY});//没法在入队时更改其值,因为现在还不确定其状态。
                        flag[newX][newY]=true;
                    }
                }
            }
        }
        return board;
    }

二、被围绕的区域

      题目链接:https://leetcode-cn.com/problems/surrounded-regions/
      题目描述:给你一个 m x n 的矩阵 board ,由若干字符 ‘X’ 和 ‘O’ ,找到所有被 ‘X’ 围绕的区域,并将这些区域里所有的 ‘O’ 用 ‘X’ 填充。

DFS 1:

      思路为:依次对每一整块‘O’区域判断,如果这一整块区域被’X’所围绕,将它们全部置为‘X’;如果这块区域中有’O’在边界上,那么此区域跳过,继续对下一块区域判断。

      由于给定一个‘O’,没法在当前就得到它是否要被变为’X’,所以将这一整块区域上’O’的位置全部保存下来,然后在DFS遍历中判断是否有’O’在边界上。参考代码如下:

    int[] addi={1,-1,0,0};
    int[] addj={0,0,1,-1};
    Queue<int[]> cache=new LinkedList<>();//用来保存某一块'O'区域的位置
    boolean flag;//为 true 时表示当前区域中有'O'在边界上
    public void solve(char[][] board) {
        int m=board.length,n=board[0].length;
        boolean[][] visited=new boolean[m][n];
        for(int i=0;i<m;i++)
            for(int j=0;j<n;j++){
                if(board[i][j]=='O'&&visited[i][j]==false){
                    flag=false;
                    dealSurround(board,i,j,visited);
                    if(flag)
                        cache.clear();//有'O'存在边界上,跳过
                    else{
                        int[] tmp;//被'X'包围时,全部变为'X'
                        while(!cache.isEmpty()){
                            tmp=cache.poll();
                            board[tmp[0]][tmp[1]]='X';
                        }
                    }
                }      
            }
    }
    void dealSurround(char[][] board,int i,int j,boolean[][] visited){//不管flag最终返回 true 或 false,必须将一整块 'O' 访问完。
        visited[i][j]=true;
        cache.add(new int[]{i,j});
        for(int k=0;k<4;k++){
            int newX=i+addi[k],newY=j+addj[k];
            if(newX<0||newY<0||newX>=board.length||newY>=board[0].length){//表示(i,j)处的'O'在边界上。
                flag=true;
                continue;
            }

            if(visited[newX][newY]==false&&board[newX][newY]=='O')
                dealSurround(board,newX,newY,visited);
        }
    }

DFS 2:

      更简单的想法:从边界上的’O’开始判断,进行DFS遍历,标记这些’O’块;然后对整个数组中未标记的’O’全部置为’X’。参考代码如下:

boolean[][] visited;
    int[] addi={1,-1,0,0};
    int[] addj={0,0,1,-1};
    public void solve(char[][] board) {
        int m=board.length,n=board[0].length;
        visited=new boolean[m][n];
        for(int i=0;i<n;i++){
            if(board[0][i]=='O')
                setVisited(board,0,i);
            if(board[m-1][i]=='O')
                setVisited(board,m-1,i);
        }
        for(int i=0;i<m;i++){
            if(board[i][0]=='O')
                setVisited(board,i,0);
            if(board[i][n-1]=='O')
                setVisited(board,i,n-1);
        }
        for(int i=0;i<m;i++)
            for(int j=0;j<n;j++){
                if(board[i][j]=='O'&&visited[i][j]==false)
                    board[i][j]='X';
            }
    }
    void setVisited(char[][] board,int i,int j){
        visited[i][j]=true;
        for(int k=0;k<4;k++){
            int newX=i+addi[k],newY=j+addj[k];
            if(newX<0||newY<0||newX>=board.length||newY>=board[0].length||board[newX][newY]=='X'||visited[newX][newY]==true)
                continue;
            setVisited(board,newX,newY);
        }
    }

总结

      完

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值