开启掘金成长之旅!这是我参与「掘金日新计划 · 12 月更文挑战」的第32天,点击查看活动详情 这也是第41篇文章
前言
本文探讨的主题是:深度优先搜索。
这个算法思想被广泛用于各自涉及图、树的题目。也形成了通用的模板。只要掌握了大体的算法思想,在遇到一道题的时候能想到可以用dfs去做,就不难了。
算法模板
talk is cheap,show me the code.
在现成的代码面前,任何言语都是苍白无力的。 下面是一个从我曾经做过的题中简单抽出的模板,里面有几句简单的注释解释为什么是那么写。
public int dfs(int [][] grid,int i,int j){
//处理边界情况
if(i<0||i>=n||j<0||j>=m||grid[i][j]!=1) return 0;
//dfs的核心步骤,在该题中是标记已访问的岛屿,避免重复访问
grid[i][j]=-1;
//代表上下左右四个方向
int [] x={1,0,-1,0};
int [] y={0,1,0,-1};
int res=1;
for(int k=0;k<4;k++){
int idx=i+x[k];
int idy=j+y[k];
res+=dfs(grid,idx,idy);
}
return res;
}
上述代码片段中,如果不想用方向数组,也可以直接用这一行代码表示:
return 1+dfs(grid,i+1,j)+dfs(grid,i,j+1)+dfs(grid,i-1,j)+dfs(grid,i,j-1);
类似地,如果是别的题,不是需要返回相加的结果,而只是依次调用dfs去搜索上下左右,关键部分就写成:
int [] x={1,0,-1,0};
int [] y={0,1,0,-1};
for(int k=0;k<4;k++){
int idx=i+x[k];
int idy=j+y[k];
dfs(grid,idx,idy);
}
或者:
dfs(grid,x+1,y);
dfs(grid,x,y+1);
dfs(grid,x-1,y);
dfs(grid,x,y-1);
接下来用一道完整的例题来感受一下:
剑指 Offer II 105. 岛屿的最大面积
题目
给定一个由
0和1组成的非空二维数组grid,用来表示海洋岛屿地图。一个 岛屿 是由一些相邻的
1(代表土地) 构成的组合,这里的「相邻」要求两个1必须在水平或者竖直方向上相邻。你可以假设grid的四个边缘都被0(代表水)包围着。找到给定的二维数组中最大的岛屿面积。如果没有岛屿,则返回面积为
0。
代码实现
class Solution {
int n;
int m;
public int maxAreaOfIsland(int[][] grid) {
n=grid.length;
m=grid[0].length;
int max=0;
for(int i=0;i<n;i++){
for(int j=0;j<m;j++){
if(grid[i][j]==1){
max=Math.max(max,dfs(grid,i,j));
}
}
}
return max;
}
public int dfs(int [][] grid,int i,int j){
if(i<0||i>=n||j<0||j>=m||grid[i][j]!=1) return 0;
grid[i][j]=-1;
return 1+dfs(grid,i+1,j)+dfs(grid,i,j+1)+dfs(grid,i-1,j)+dfs(grid,i,j-1);
}
}
然而,dfs并不是独立使用的,它经常和“回溯”、“剪枝”等等结合起来使用。
- 所谓回溯,就是到了下一个状态后又返回上一个状态;
- 所谓剪枝,就是因为深度优先搜索其实也是暴力搜索的一种形式,如果数据量太大,很容易导致超时,这时候就需要人为识别一些没必要搜索的状态,并且避免去搜索它。
比如下面这题: (剪枝我记得有做过的,但是现在一下找不到了,等找到再补充)
剑指 Offer II 110. 所有路径
题目
给定一个有
n个节点的有向无环图,用二维数组graph表示,请找到所有从0到n-1的路径并输出(不要求按顺序)。
graph的第i个数组中的单元都表示有向图中i号节点所能到达的下一些结点(译者注:有向图是有方向的,即规定了 a→b 你就不能从 b→a ),若为空,就是没有下一个节点了。
思路
dfs+回溯
代码实现(含少量注释)
class Solution {
int [][] graph;
int n;
List<List<Integer>> res=new ArrayList<>();
List<Integer> tmp=new ArrayList<>();
public List<List<Integer>> allPathsSourceTarget(int[][] graph) {
this.graph=graph;
n=graph.length;
tmp.add(0);
dfs(0);
return res;
}
public void dfs(int index){
//递归结束条件
if(index==n-1){
res.add(new ArrayList<Integer>(tmp));
return ;
}
//遍历搜索+回溯
for(int val:graph[index]){
tmp.add(val);
dfs(val);
tmp.remove(tmp.size()-1);
}
}
}