LeetCode.130被围绕的区域
难度:中等
目录
作者原始思路
深度优先之失败版
class Solution {
public boolean isChange;
public void solve(char[][] board) {
var isVisited = new boolean[board.length][board[0].length];
if (board.length < 3 || board[0].length < 3) {
return;
}
for (int i = 0; i < board.length - 1; i++) {
for (int j = 0; j < board[i].length - 1; j++) {
if (board[i][j] == 'O' && !isVisited[i][j]) {
isChange = true;
dfs(board,isVisited,i,j);
}
}
}
}
private void dfs(char[][] board, boolean[][] isVisited, int curR, int curC) {
isVisited[curR][curC] = true;
int[] des = get(curR,curC,isVisited,board);
if (des != null) {
if (des[0] == 0 || des[0] == board.length - 1 || des[1] == 0 || des[1] == board[0].length - 1) {
isChange = false;
}
dfs(board,isVisited,des[0],des[1]);
}
if (isChange) {
board[curR][curC] = 'X';
}
}
private int[] get(int curR, int curC, boolean[][] isVisited,char[][] board) {
if (curC >= board[0].length - 1 || curR >= board.length - 1) {
return null;
}
if (!isVisited[curR][curC+1] && board[curR][curC+1] == 'O') {
return new int[] {curR,curC+1};
}else if (!isVisited[curR+1][curC] && board[curR+1][curC] == 'O') {
return new int[] {curR+1,curC};
}else if (!isVisited[curR-1][curC] && board[curR+-1][curC] == 'O') {
return new int[] {curR-1,curC};
}else if (!isVisited[curR][curC-1] && board[curR][curC-1] == 'O') {
return new int[] {curR,curC-1};
}
else {
return null;
}
}
}
思想与代码简述
- 1. 首先,我们先看数组是否少于三列或三行,如果少于三列或三行就直接返回(因为两列以内一定不可能有O被围绕)
- 2.通过外部循环,我们找到第一个是O的元素的位置,然后找到该位置上所有与他相邻的O的位置,即DFS
- 3.注意的是:为了避免这层循环重复访问元素,我们可以建立一个状态数组判断其是否被访问过,如果没有被访问过再进行
- 4.isChange每次都初始化为true,表示可以改变
- 5.进入dfs,先让当前元素设置成已访问,然后得到下一个位置,即get方法,先判断边界问题,然后直接返回下一个位置,如果没有则返回空
- 注意:这里用数组来存储位置
- 6.如果得到了位置,先判断该位置是否为空,如果不为空再判断是否在边界,如果在边界则设置为isChange = false表示不可改变
- 7.继续向下dfs,最后根据isChange改变即可
问题
- 该方法理论上是可以的,但是,经过了我的努力,终于看出了问题,
- 1.即在get方法中的边界问题没有处理好,导致找到一半找不下去了
- 2.代码超级冗余,我自己都看不下去了,太难受了
- 3.上述的两个问题导致案例只通过了一半,而且写法超丑
反省
- 对于DFS深度优先算法,我们应该学会不用这么多判断语句去往下走,我们应该灵活的利用DFS深入
官方解法-DFS深度优先搜索算法
题目分析
- 题目中要求我们修改被X围绕的O的元素,将这些元素都改为X,我们发现,只要这些O直接或间接的接触到边界上的O,这些O将无法改变,即有边界O的参与,所有涉及的O都无法改变
- 所以,我们不妨从边界开始递归搜索,找到与边界O所有相邻的O
好处(重要)
- 如果我们从边界开始搜索,那么我们递归找到的肯定都是不能够修改的O,不需要判断,我们就省去了找可以修改的O,减少递归次数,算法效率提高
- 如果我们从里面开始搜索,我们不仅找得到可以改的,也可以找得到不可以改的,这就是博主一开始的思想,要判断是否可以改算法效率极差
算法思想
1.利用DFS深度优先搜索开始搜索边界的O
代码实现
class Solution {
public int row;
public int col;
public void solve(char[][] board) {
row = board.length;
col = board[0].length;
for (int i = 0; i < col; i++) {
dfs(board,0,i);
dfs(board,row - 1,i);
}
for (int i = 0; i < row; i++) {
dfs(board,i,0);
dfs(board,i,col-1);
}
for (int i = 0; i < row ; i++) {
for (int j = 0; j < col; j++) {
if (board[i][j] == ' ') {
board[i][j] = 'O';
}else if (board[i][j] == 'O') {
board[i][j] = 'X';
}
}
}
}
private void dfs(char[][] board, int r, int c) {
if (r > row - 1 || r < 0 || c > col - 1 || c < 0 || board[r][c] != 'O') {
return;
}
board[r][c] = ' ';
dfs(board,r+1,c);
dfs(board,r,c+1);
dfs(board,r-1,c);
dfs(board,r,c-1);
}
}
代码分析
- 1.建立两个属性,分别是row与col,用于记录对应的行与列,减少递归传入的参数,美化代码
- 即:如果以后发现,在DFS中,每次都要传递的参数都一样,我们可以考虑将其设置为属性
- 2.前面两个for循环,对应着从边界开始去深度优先
- 3.深度优先dfs
- 3.1参数
- board即题目所给的画板
- r为当前的行的下标
- c为当前的列的下标
- 3.2先判断边界情况,r的边界无非是0与row - 1,c的边界无非是0和col-1,如果超过这个边界说明不能走了,直接弹栈
- 3.3注意这里还有一个!='O'的条件,如果不等于'O',说明与边界O相邻的O在当前位置上没有了,可以直接弹栈
- 3.4将该位置设置为' '
- 3.5向上下左右去走即可
- 3.1参数
- 4.最后一个for循环,再遍历一次board
- 4.1如果发现该位置为' ',说明这肯定是DFS深度优先搜索与边界O相邻的O,这些O都不可以改变成X,所以直接赋值O即可
- 4.2如果发现该位置为'O',说明这肯定没有被DFS深度优先搜索到,说明不是边界O相邻的O,即被包围的O,赋值X,表示更改
结论
深度优先不仅可以创建树,还可以去弄各种各样的东西,所以,积硅步,至千里,最后我来总结必须要掌握的几点
1.为什么从边界遍历开始,有什么优势
2.dfs算法的实现