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’ ),表示相邻地雷的数量。
如果在此次点击中,若无更多方块可被揭露,则返回盘面。
思路为: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 中,为了避免相同元素重复入队有两种解决方法: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’ 填充。
思路为:依次对每一整块‘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);
}
}
更简单的想法:从边界上的’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);
}
}
完