DFS和BFS专题
130. 被围绕的区域
总的思想是寻找边界条件,使用标记数组。
class Solution {
// 将需要的变量设置为全局变量
char[][] board;
int rows;
int cols;
boolean[][] visited;
public void solve(char[][] board) {
/**
分析:
题目中提示的很明显要处理边界条件。
边界条件具体来说包括四个:第一行,第一列,最后一行,最后一列。和这四个边界相邻的O也要处理。
那么可以使用一个visisted二维数组来标记是否是边界满足值
*/
this.board = board;
rows = board.length;
cols = board[0].length;
this.visited = new boolean[rows][cols];
// 遍历第一行和最后一行
for(int i = 0; i < cols; i++){
dfs(0,i);
dfs(rows - 1,i);
}
// 遍历第一列和最后一列
for(int j = 0; j < rows; j++){
dfs(j,0);
dfs( j,cols - 1);
}
// 遍历整个二维数组
for(int i = 1; i < rows; i++){
for(int j = 1; j < cols; j++){
// 发现非边界值O
if(board[i][j] == 'O' && !visited[i][j]){
board[i][j] = 'X';
}
}
}
}
public void dfs(int row,int col){
// 不在区域内,不等于O,已经访问过的,直接return掉
if(!inArea(row,col) || board[row][col] != 'O' || visited[row][col]){
return;
}
visited[row][col] = true;
dfs(row+1,col);
dfs(row-1,col);
dfs(row,col-1);
dfs(row,col+1);
}
public boolean inArea(int row,int col){
return row>=0 && row < rows && col >= 0 && col<cols;
}
}
200. 岛屿数量(dfs)
class Solution {
boolean[][] visited;
public int numIslands(char[][] grid) {
/**
分析:
和130题的思路一致,使用标记数组标记是否已经遍历过
*/
int rows = grid.length;
int cols = grid[0].length;
this.visited = new boolean[rows][cols];
int num = 0;
for(int i = 0; i < rows; i++){
for(int j = 0; j < cols; j++){
if(grid[i][j] == '1' && !visited[i][j]){
// 是陆地并且没有被标记过,则dfs一下,并且+1
dfs(grid,i,j);
num++;
}
}
}
return num;
}
public void dfs(char[][] grid,int row ,int col){
// 递归出口是跑出边界外,不为1,已经被访问过了
if(row < 0 || row >= grid.length || col < 0 ||col >= grid[0].length){
return ;
}
if(grid[row][col] != '1' || visited[row][col]) return;
// 标记访问
visited[row][col] = true;
// 四个方向递归遍历
dfs(grid,row+1,col);
dfs(grid,row-1,col);
dfs(grid,row,col+1);
dfs(grid,row,col-1);
}
}
429. N 叉树的层序遍历(bfs)
就是使用普通的bfs
/*
// Definition for a Node.
class Node {
public int val;
public List<Node> children;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, List<Node> _children) {
val = _val;
children = _children;
}
};
*/
class Solution {
public List<List<Integer>> levelOrder(Node root) {
List<List<Integer>> res = new ArrayList<>();
if(root == null) return res;
Queue<Node> queue = new LinkedList<>();
queue.offer(root);
while( !queue.isEmpty()){
int size = queue.size();
List<Integer> temp = new ArrayList<>();
for(int i = 0; i < size; i++){
Node cur = queue.poll();
temp.add(cur.val);
if(cur.children != null){
// 孩子节点依次进队
for(int j = 0; j < cur.children.size(); j++){
queue.offer(cur.children.get(j));
}
}
}
res.add(temp);
}
return res;
}
}
463. 岛屿的周长
本题有两种解法,第一种解法不具备普适性,思路清奇。使用普通遍历,遇到小岛(‘1’)就累加,同时判断右侧和下侧是否有小岛,有的话累加边。最后的结果就是4*小岛个数 - 2 * 公共边。
第二种解法具备普适性,设置访问数组,在遍历过程中,遇到已访问陆地,则周长不变,return 0,遇到水域或者越界了,返回周长1 ,最后通过四个方向累加周长,返回周长。示意图如下:
解法一:普通遍历+数学公式
class Solution {
public int islandPerimeter(int[][] grid) {
/**
分析:
普通遍历,还得判定相应的数学公式:4*小岛个数 - 2 * 公共边
*/
if(grid == null || grid.length == 0) return 0;
int rows = grid.length;
int cols = grid[0].length;
int isLand = 0;
int edge = 0;
// 普通遍历
for(int i = 0; i < rows; i++){
for(int j = 0; j < cols; j++){
if(grid[i][j] == 1){
// 遇到了 陆地
isLand++;
// 判断下侧和右边有没有小岛
if(i < rows - 1 && grid[i+1][j] == 1) {
// 边+1
edge++;
}
if( j < cols - 1 && grid[i][j+1] == 1){
edge++;
}
}
}
}
return 4 * isLand - 2 * edge;
}
}
解法二:dfs+标记数组
class Solution {
public int islandPerimeter(int[][] grid) {
// 现在使用dfs遍历法,思路是遇到水域或者遇到边界,那么周长就+1
int rows = grid.length;
int cols = grid[0].length;
boolean[][] visited = new boolean[rows][cols];
for(int i = 0; i < rows; i++){
for(int j = 0; j < cols; j++){
if(grid[i][j] == 1 && !visited[i][j]){
return dfs(grid,i,j,visited);
}
}
}
return 0;
}
public int dfs(int[][] grid,int i,int j,boolean[][] visited){
// 遇到边界了
if(i < 0 || i >= grid.length || j < 0 || j >= grid[0].length) return 1;
// 遇到水域了
if(grid[i][j] == 0) return 1;
// 遇到已经被访问的小岛
if(visited[i][j]) return 0;
// 标记访问
visited[i][j] = true;
// 定义周长
int res = 0;
// 四个方向递归遍历
res += dfs(grid,i+1,j,visited);
res += dfs(grid,i-1,j,visited);
res += dfs(grid,i,j+1,visited);
res += dfs(grid,i,j-1,visited);
return res;
}
}
589. N 叉树的前序遍历
没啥好说的,理解清除递归逻辑,很容易写出来
/*
// Definition for a Node.
class Node {
public int val;
public List<Node> children;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, List<Node> _children) {
val = _val;
children = _children;
}
};
*/
class Solution {
List<Integer> res = new ArrayList<>();
public List<Integer> preorder(Node root) {
if(root == null) return res;
dfs(root);
return res;
}
public void dfs(Node root){
if(root == null) return;
res.add(root.val);
for(int i = 0; i < root.children.size(); i++){
dfs(root.children.get(i));
}
}
}
590. N 叉树的后序遍历
/*
// Definition for a Node.
class Node {
public int val;
public List<Node> children;
public Node() {}
public Node(int _val) {
val = _val;
}
public Node(int _val, List<Node> _children) {
val = _val;
children = _children;
}
};
*/
class Solution {
List<Integer> res = new ArrayList<>();
public List<Integer> postorder(Node root) {
if(root == null) return res;
dfs(root);
return res;
}
public void dfs(Node root){
if(root == null) return;
if(root.children != null){
for(int i = 0; i < root.children.size(); i++){
dfs(root.children.get(i));
}
}
res.add(root.val);
}
}
695. 岛屿的最大面积
总体来说是套小岛问题的模板
class Solution {
boolean[][] visited;
public int maxAreaOfIsland(int[][] grid) {
/**
分析:
和之前求岛屿数量有一点区别,就是这里是不断更新最大岛屿数目的
*/
if(grid == null || grid.length == 0) return 0;
int rows = grid.length;
int cols = grid[0].length;
visited = new boolean[rows][cols];
int res = 0;
for(int i = 0; i < rows; i++){
for(int j = 0; j < cols; j++){
if(grid[i][j] == 1 && !visited[i][j]){
// 每次更新搜索到的最大小岛数目
res = Math.max(res,dfs(grid,i,j)) ;
}
}
}
return res;
}
public int dfs(int[][] grid, int i,int j){
// 越界了
if( i < 0 || i >= grid.length || j <0 || j >= grid[0].length) return 0;
// 遇到水或者已经访问过了
if(grid[i][j] == 0 || visited[i][j]) return 0;
int maxIsland = 0;
// 设置标记
visited[i][j] = true;
// 四个方向遍历
maxIsland += dfs(grid,i+1,j);
maxIsland += dfs(grid,i-1,j);
maxIsland += dfs(grid,i,j+1);
maxIsland += dfs(grid,i,j-1);
// 这里很关键,本层遍历成功,小岛个数要+1
return maxIsland +1;
}
}
994. 腐烂的橘子(多源BFS)
class Solution {
public int orangesRotting(int[][] grid) {
// 边界 长宽
int M = grid.length;
int N = grid[0].length;
Queue<int[]> queue = new LinkedList<>();
// count 表示新鲜橘子的数量
int count = 0;
// 遍历二维数组 找出所有的新鲜橘子和腐烂的橘子
for (int r = 0; r < M; r++) {
for (int c = 0; c < N; c++) {
// 新鲜橘子计数
if (grid[r][c] == 1) {
count++;
// 腐烂的橘子就放进队列
} else if (grid[r][c] == 2) {
// 缓存腐烂橘子的坐标
queue.add(new int[]{r, c});
}
}
}
// round 表示腐烂的轮数,或者分钟数
int round = 0;
// 如果有新鲜橘子 并且 队列不为空
// 直到上下左右都触及边界 或者 被感染的橘子已经遍历完
while (count > 0 && !queue.isEmpty()) {
// BFS 层级 + 1
round++;
// 拿到当前层级的腐烂橘子数量, 因为每个层级会更新队列
int n = queue.size();
// 遍历当前层级的队列
for (int i = 0; i < n; i++) {
// 踢出队列(拿出一个腐烂的橘子)
int[] orange = queue.poll();
// 恢复橘子坐标
int r = orange[0];
int c = orange[1];
// ↑ 上邻点 判断是否边界 并且 上方是否是健康的橘子
if (r-1 >= 0 && grid[r-1][c] == 1) {
// 感染它
grid[r-1][c] = 2;
// 好橘子 -1
count--;
// 把被感染的橘子放进队列 并缓存
queue.add(new int[]{r-1, c});
}
// ↓ 下邻点 同上
if (r+1 < M && grid[r+1][c] == 1) {
grid[r+1][c] = 2;
count--;
queue.add(new int[]{r+1, c});
}
// ← 左邻点 同上
if (c-1 >= 0 && grid[r][c-1] == 1) {
grid[r][c-1] = 2;
count--;
queue.add(new int[]{r, c-1});
}
// → 右邻点 同上
if (c+1 < N && grid[r][c+1] == 1) {
grid[r][c+1] = 2;
count--;
queue.add(new int[]{r, c+1});
}
}
}
// 如果此时还有健康的橘子
// 返回 -1
// 否则 返回层级
if (count > 0) {
return -1;
} else {
return round;
}
}
}