【LeetCode刷题笔记】(七)dfs

205 阅读3分钟

开启掘金成长之旅!这是我参与「掘金日新计划 · 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);
        }
    }
}