强大的dfs和bfs算法,各适合何种场景,以及Java实现

我是一个从汽车行业转行IT的项目经理,我是Edward。今天我们来聊一下,dfs和bfs,连着两天leetcode每日一题给到dfs,着实让我领略到了dfs查找功能的强大,那么我们先一起来看看这两道题。

22. 括号生成

数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。

示例:

输入:n = 3
输出:[
“((()))”,
“(()())”,
“(())()”,
“()(())”,
“()()()”
];

package algorithm;
/**
 * 强大的dfs查找
* @author EP
* <p>22. 括号生成<p>
* @date 2020年4月9日  
* @version 1.0
 */

import java.util.ArrayList;
import java.util.List;

public class Solution_generateParenthesis {

	public static void main(String[] args) {
		int n = 5;
		res = generateParenthesis(n);
		for (String string : res) {
			System.out.println(string);
		}

	}
	static List<String>res = new ArrayList<String>();
    public static List<String> generateParenthesis(int n) {
    	search(n, n, "");
    	return res;
    }
    private static void search(int left,int right,String curString) {
		if (left==0&&right==0) {  //递归终止的base case,左右都不剩即终止
			res.add(curString);
			return;
		}
		if (left>0) {  //如果左括号还剩余,可以拼接左括号
			search(left-1, right, curString+"(");
		}
		if (right>left) { //如果右括号剩余多于左括号剩余,可以拼接右括号
			search(left, right-1, curString+")");
		}
	}

}

可以看到,只要设定了规则,dfs就可以不断递归帮你完成深度查找。

面试题13. 机器人的运动范围

地上有一个m行n列的方格,从坐标 [0,0] 到坐标 [m-1,n-1] 。一个机器人
从坐标 [0, 0] 的格子开始移动,它每次可以向左、右、上、下移动一格(不能
移动到方格外),也不能进入行坐标和列坐标的数位之和大于k的格子。
例如,当k为18时,机器人能够进入方格 [35, 37] ,因为3+5+3+7=18。
但它不能进入方格 [35, 38],因为3+5+3+8=19。请问该机器人能够到达多少个格子?

示例 1:

输入:m = 2, n = 3, k = 1
输出:3

提示:

1 <= n,m <= 100 0 <= k <= 20

public class Solution_movingCount {

	public static void main(String[] args) {
		int m=2,n=3,k=1;
		System.out.println(movingCount(m, n, k));	
	}
	
	static boolean[][]visited;
    public static int movingCount(int m, int n, int k) {   
    	//设置boolean数组标记访问状态
    	visited = new boolean[m][n];
    	//设置递归查找方法
    	return search(0, 0, m, n, k);
    }
    //据说是dfs
    private static int search(int x,int y,int m,int n,int k) {
    	//设置递归终止的base case
		if (x>=m||y>=n||visited[x][y]||(x%10+x/10+y%10+y/10)>k) {
			return 0;
		}
		//其他情况则标记、+1、继续找
		visited[x][y]=true;
		return 1+search(x+1, y, m, n, k)+search(x, y+1, m, n, k);
	}
}

这道面试题中也涉及到了dfs查找需要注意的一点,就是设置标记,记录访问状态。

再来看看bfs的场景:1162. 地图分析

  • 你现在手里有一份大小为 N x N 的『地图』(网格) grid,上面的每个『区域』(单元格)都用 0 和 1 标
  • 记好了。其中 0 代表海洋,1 代表陆地,你知道距离陆地区域最远的海洋区域是是哪一个吗?请返回该海
  • 洋区域到离它最近的陆地区域的距离。

我们这里说的距离是『曼哈顿距离』( Manhattan Distance):(x0, y0) 和 (x1, y1) 这两个
区域之间的距离是 |x0 - x1| + |y0 - y1| 。

如果我们的地图上只有陆地或者海洋,请返回 -1。

示例 1:

输入:[[1,0,1],[0,0,0],[1,0,1]] 输出:2 解释: 海洋区域 (1, 1) 和所有陆地区域之间的距离都达到最大,最大距离为 2。

提示:

1 <= grid.length == grid[0].length <= 100
grid[i][j] 不是 0 就是 1

public class Solution_maxDistance {
	
	

	public static void main(String[] args) {
		int[][]grid={{1,0,1},{0,0,0},{1,0,1}};
		System.out.println(maxDistance(grid));
		

	}
    public static int maxDistance(int[][] grid) {
    	int[]dx= {0,0,1,-1};
    	int[]dy= {1,-1,0,0};   //建立四个方向
    	
    	Queue<int[]>queue=new ArrayDeque<>();    //建立新队列
    	int m  =grid.length, n=grid[0].length;
    	//先把所有陆地入队
    	for (int i = 0; i < m; i++) {
			for (int j = 0; j < n; j++) {
				if (grid[i][j]==1) {
					queue.offer(new int[] {i,j});
				}
			}
		}
    	
    	//从各个陆地开始,一圈一圈地遍历海洋,最后遍历到的海洋就是离陆地最远的海洋
    	boolean hasOcean=false;
    	int[]point=null;
    	while (!queue.isEmpty()) {
			point =queue.poll();    //建立空数列接收queue里面返回的陆地
			int x = point[0], y=point[1];
			//将取出队列的元素的四周的海洋入队
			for (int i = 0; i < 4; i++) {
				int newX = x+dx[i];
				int newY = y+dy[i];
				if (newX<0||newX>=m||newY<0||newY>=n||grid[newX][newY]!=0) {  //当超出范围或者不为海洋时跳过
					continue;
				}
				//在原数组的基础上进行了更新,对访问过的海洋进行了标记
				grid[newX][newY]=grid[x][y]+1; 
				hasOcean = true;
				queue.offer(new int[] {newX,newY});  //将新找到的海洋入队
			}
			
		}
    	//没有陆地或者没有海洋,返回-1
    	if (!hasOcean||point==null) {
			return -1;
		}
    	//返回最后一次遍历到的海洋的距离
    	return grid[point[0]][point[1]]-1;
    	
    	
    }

}

可以看到bfs主要的适用场景是判断最大或最小路径以及可达性分析,以前不太理解为什么图解算法上要把队列和bfs放在一起,这下算明白了,就像是派遣舰队,依次把舰只派遣出去搜寻未知的土地,然后再将搜到的资源依次归队,再次出队。

再来看看这道题,是4月15日的打卡题,咋看之下跟地图分析非常类似:

542. 01 矩阵

给定一个由 0 和 1 组成的矩阵,找出每个元素到最近的 0 的距离。

两个相邻元素间的距离为 1 。

示例 1:
输入:

0 0 0
0 1 0
0 0 0
输出:

0 0 0
0 1 0
0 0 0
示例 2:
输入:

0 0 0
0 1 0
1 1 1
输出:

0 0 0
0 1 0
1 2 1
注意:

给定矩阵的元素个数不超过 10000。
给定矩阵中至少有一个元素是 0。
矩阵中的元素只在四个方向上相邻: 上、下、左、右。

    	//努力按照maxDistance的思路改了一下还是失败了,看看sweetie改的
        // 首先将所有的 0 都入队,并且将 1 的位置设置成 -1,表示该位置是 未被访问过的 1
        Queue<int[]> queue = new LinkedList<>();
        int m = matrix.length, n = matrix[0].length;
        for (int i = 0; i < m; i++) {
            for (int j = 0; j < n; j++) {
                if (matrix[i][j] == 0) {
                    queue.offer(new int[] {i, j});
                } else {
                    matrix[i][j] = -1;
        //(设成Integer.MAX_VALUE啦,m * n啦,10000啦都行,只要是个无效的距离值来标志这个位置的 1 没有被访问过就行辣~)
                } 
            }
        }
        
        int[] dx = new int[] {-1, 1, 0, 0};
        int[] dy = new int[] {0, 0, -1, 1};
        while (!queue.isEmpty()) {
            int[] point = queue.poll();
            int x = point[0], y = point[1];
            for (int i = 0; i < 4; i++) {
                int newX = x + dx[i];
                int newY = y + dy[i];
                // 如果四邻域中的某点是 -1,则表示该点是未被访问过的 1
                // 所以这个点到 0 的距离就可以更新成 matrix[x][y] + 1
                // 然后将该点入队继续查找
                // 这样每个点就只会被入队一次
                if (newX >= 0 && newX < m && newY >= 0 && newY < n 
                        && matrix[newX][newY] == -1) {
                    matrix[newX][newY] = matrix[x][y] + 1;
                    queue.offer(new int[] {newX, newY});
                }
            }
        }

        return matrix;

自己实现起来并不容易,我尝试在地图分析源码上进行修改,但是失败了,总结一下几个关键点:
1:先入队的元素如何选.虽然每个点都要入队,但是先入队的点决定了后续逻辑,二选一的话,最好选本身值不变的点,bfs就可以直接更新四周的值.
2:标记未访问过的点,原则上一个点只入队一次.比如这题,未访问过的点已为1,就先初始化为-1,访问过再变为1入队.

这里也推荐一个大佬,甜姨,她每天都会在半夜半小时之内完成leetcode打卡,她的公众号【甜姨的奇妙冒险】和 知乎专栏【甜姨的力扣题解】,希望更多人领略到算法之美。

原创文章 46 获赞 7 访问量 2077

猜你喜欢

转载自blog.csdn.net/EdwardWH/article/details/105407234