DFS常规解题套路

0 前言

昨天突然到来的代码训练营中,我被叫起来讲两周前的一道题,有点懵,有同学听完之后表示没太明白,可能我当时表述的比较着急所以没讲清楚。现在特别整理了一下DFS的解题模板,并挑选了一系列leetcode的相关题目(从easy到hard),希望大家看完之后能对DFS有个更好的认识。
本文内容比较基础,只适用于对DFS了解不深的同学;不过欢迎所有的同学交流和指正,大家一起努力提高~

1 DFS简介:

引用自leetcode网站关于DFS的介绍

深度优先搜索算法(英语:Depth-First-Search,DFS)是一种用于遍历或搜索树或图的算法。沿着树的深度遍历树的节点,尽可能深的搜索树的分支。当节点v的所在边都己被探寻过,搜索将回溯到发现节点v的那条边的起始节点。这一过程一直进行到已发现从源节点可达的所有节点为止。如果还存在未被发现的节点,则选择其中一个作为源节点并重复以上过程,整个进程反复进行直到所有节点都被访问为止。属于盲目搜索。
深度优先搜索是图论中的经典算法,利用深度优先搜索算法可以产生目标图的相应拓扑排序表,利用拓扑排序表可以方便的解决很多相关的图论问题,如最大路径问题等等。
因发明「深度优先搜索算法」,约翰 · 霍普克洛夫特与罗伯特 · 塔扬在1986年共同获得计算机领域的最高奖:图灵奖。

2 DFS模板

DFS的一般模板(解题一般套路):

//参数用来表示当前状态; 
//返回值是我们dfs完成之后想要获取的数据,如果不需要返回值或者通过全局变量来记录状态的话ReturnType可以为void
//函数名可以换成更有意义的名字
ReturnType dfs(param1,params2,...) 
{
     
    if(终点状态 || 非法状态 || 需要剪枝)  
    {
     
        ... //退出前处理
        return;  
    }  
    for(每一个当前状态相关的下一个状态)  
    {
     
        if(该状态合法 && 该状态未被标记)  
        {
     
            ...; // 当前状态应该做的处理(遍历前需要的处理)(根据实际情况来判断是否需要)
            标记当前状态;  
            dfs();  
            ...; // 当前状态应该做的处理(遍历后需要的处理)(根据实际情况来判断是否需要)
            (还原标记); //可选操作, 如果加上这句就是"回溯法"  
        }  
 
    }  
}  

3 DFS实战

我们从一系列实战例题来逐步加深对DFS模板的理解。
说明:实战部分的代码均为博主手敲,主要是用来和大家一起熟悉思路,可能不是最优雅的解法。

实战一:简单DFS

题目: LeetCode No.100 相同的树 (简单) 原题链接

给定两个二叉树,编写一个函数来检验它们是否相同。如果两个树在结构上相同,并且节点具有相同的值,则认为它们是相同的。

题目分析
树的遍历,可以用dfs解决。从根结点出发,如果根结点相同 && 根结点的左子树相同 && 根结点的右子树相同,则可以判断两个二叉树相同。
java代码
应用DFS模板很容易写出下面的代码:

class Solution {
   
// 当前状态(两个树同一位置的某个节点)可用参数p和q表示;返回值(是否相同)显然是boolean
    public boolean isSameTree(TreeNode p, TreeNode q) {
   
    // 终止状态 直接返回
    if (p == null && q == null) return true;
    if (p == null || q == null) retrun false;
    if (p.val != q.val) return false;
    // 当前状态相关的下一个状态有两个: 比较树的左子和右子
    // 因为当前节点不会再次遍历,省略当前状态的标记处理和标记还原操作
    boolean leftSame = isSameTree(p.left, q.left);
    boolean rightSame = isSameTree(p.right, q.right);
    // 遍历后需要的处理
    return leftSame && rightSame;
    }
}

实战二:稍复杂的DFS

题目: LeetCode No.112 路径总和 (简单) 原题链接

给定一个二叉树和一个目标和,判断该树中是否存在根节点到叶子节点的路径,这条路径上所有节点值相加等于目标和。
说明: 叶子节点是指没有子节点的节点。

题目分析
树的遍历,可以用dfs解决。从根结点出发,如果 (根结点.val == 目标和 && 根结点为叶子节点) || (根结点.val + 根结点的左子树.val == 目标和) || (根结点.val + 根结点的右子树.val == 目标和),则可以判断存在满足题意的路径。
java代码
应用DFS模板很容易写出下面的代码:

class Solution {
   
// 当前状态除了树的当前节点root,还有当前期望的和sum;
// 返回值(是否存在路径)显然是boolean
    public boolean hasPathSum(TreeNode root, int sum) {
   
    // 终止状态 直接返回
    if (root.left == null && root.right == null && root.val == sum) {
   
        return true;
    }
    // 当前状态相关的下一个状态有两个: 比较树的左子和右子
    // 邻节点dfs之前应该做的处理(设定expectSum)
    int expectSum = sum - root.val;
    // 因为当前节点不会再次遍历,省略当前状态的标记处理和标记还原操作
    boolean leftRes = hasPathSum(root.left, expectSum);
    boolean rightRes = hasPathSum(root.right, expectSum);
    // 遍历后需要的处理
    return leftSame && rightSame;
}

相比较前一题,本题在dfs时除了关注树本身节点外还需要关注当前期望和sum,这里刚开始学习dfs的同学可能会觉得有一点绕,理解的关键还是要搞清楚dfs遍历时都有哪些数据在发生变化(刚开始初学时,如果不确定dfs方法需要哪些参数,可以把这些会发生变化的数据都当作方法参数) 。刚开始学习dfs的部分同学对于dfs执行的顺序也可能感到有点难理解,这个问题可以通过不断练习针对不同的输入调试跟踪dfs遍历的过程来解决。

实战三:DFS+回溯

题目: LeetCode No.113 路径总和 II (中等) 原题链接

给定一个二叉树和一个目标和,找到所有从根节点到叶子节点路径总和等于给定目标和的路径。
说明: 叶子节点是指没有子节点的节点。

题目分析
遍历树可以得到所有满足条件的路径,可以用dfs解决。
从根结点出发对树进行完整遍历,如果 (当前节点为叶子结点 && 从叶子节点往上所有祖先.val之和 == 目标和),则将该路径加入到结果集合。
仔细思考dfs的过程,和当前状态有关的变量可能有:当前的节点root、当前目标和sum、当前路径path、当前结果集res。其中与当前状态强相关的变量是:当前的节点root、当前目标和sum;起支持作用的变量是:当前路径path、当前结果集res。一般习惯将强相关的变量放到dfs的参数列表中;起支持作用的变量可以放到dfs参数列表中,也可以放到全局变量(之后dfs过程中能用到就好)。
java代码
应用DFS模板很容易写出下面的代码:

class Solution {
   
    // 用全局变量res来记录结果(当然也可以将res当作当前状态的一部分放到dfs的参数列表中)。
    private List<List<Integer>> res 

你可能感兴趣的:(算法,算法)