三步完成DFS(深度优先搜索)通用模板


前言

本文目的是在刷leetcode 的时候,突然不能一下子明白dfs 怎么写出来,故总结快速解决 DFS 问题的思路。通过递归和树,完成DFS模版, 快速解决简单暴力的DFS 问题,不包括减枝等优化,模板在文章最末尾。


一、DFS 适用于什么场景?

碰到问题,如果你解决问题的思路可以用树状图表示的时候,即可使用DFS算法去解决。
例如: leetcode 第 17 题

给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。

给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。

 

示例 1:

输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]

示例 2:

输入:digits = ""
输出:[]

示例 3:

输入:digits = "2"
输出:["a","b","c"]

 

提示:

    0 <= digits.length <= 4
    digits[i] 是范围 ['2', '9'] 的一个数字。

来源:力扣(LeetCode)
链接:https://leetcode.cn/problems/letter-combinations-of-a-phone-number
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。

img
我们可以自然的联想到解决该问题的步骤:
img

二、三步完成DFS

步骤一: 通过树状图的深度,快速找到递归的出口

  • 什么叫做出口?
    以leetcode 的17 题作为例子, 输入"23" 我们需要得到结果为
["ad","ae","af","bd","be","bf","cd","ce","cf"]

那么,在这里的出口就是得到 每个item的结果的时候. 即你能得到 ad这个结果, ae 这个结果…
出口可以解释为,已经得到其中一个结果, 不需要继续递归, 需要跳出递归时候的出口

  • 把树的深度作为出口
    当你把问题简化成树状图的时候,树的深度是一个很好的出口, 即当深度到达一定程度就不再递归。
    以leetcode 的17题作为例子, 当高低降到 0 的时候, 不再递归, 直接return, 同时把其中一个结果存储起来。combine里面存的是结果, 其中出口一定要作为参数传进去。
void DFS(int high)
{
    if(hight == 0)
    {
        combine.push_back(cur);
        return; 
    }
}

步骤二: 快速确定递归逻辑

快速确定递归的逻辑在于快速确定递归函数需要的参数。
以leetcode 17题作为例子最终需要的的参数是

void DFS(int high, string digits, string cur)
  • 第一个参数树状图的深度,也是出口, 当深度降为 0 的时候, 就可与跳出循环。
  • 第二个参数是树状图的枝条, 根据树状图, 每个枝条都是一个数据, 我们需要得到每个枝条上的数据
  • 第三个参数是树状图的节点, 根据树状图,没有子节点的节点是其中的答案

最终的逻辑是 每递归一次, high 减1, 同时节点里面加上枝条上的一个数据, 当 high 减到0 的时候, 节点里面填的值刚好就是一个结果。

步骤三: 快速递归分枝

以leetcode 17题作为例子, 分枝是根据电话按键里面的string 进行分枝的故
得出的分枝代码是

    for( int i = 0 ; i < phoneMap.at(tempd).size(); ++i)
    {
        cur = tempc + phoneMap[tempd][i];
        DFS(hight, digits, cur);
    }

其中 phoneMap.at(tempd).size() 是为了得到枝条数目。分枝的过程中, 需要把结果得出来, 同时,不应当在分枝的过程中改变 枝条和深度, 目的是为了保证递归找到一个出口后还能安全回退。

leetcode 17题全部答案

class Solution {
public:
    unordered_map<char, string> phoneMap{
        {'2', "abc"},
        {'3', "def"},
        {'4', "ghi"},
        {'5', "jkl"},
        {'6', "mno"},
        {'7', "pqrs"},
        {'8', "tuv"},
        {'9', "wxyz"}
    };
    vector<string> combine;
    //--------------------
    // 梦开始的地方
    //--------------------
    void DFS(int high, string digits, string cur)
    {
    // 步骤一:找到出口 
        if(high == 0)
        {
            combine.push_back(cur);
            return; 
        }
    //不能影响分枝操作,需要把数据存到temp 中
        int temph =high;
        char tempd =digits[0]; 
        high-- ;
        digits = digits.substr(1);
        string tempc = cur;
   // 分枝操作,里面只能进行对节点的操作
        for( int i = 0 ; i < phoneMap.at(tempd).size(); ++i)
        {
            cur = tempc + phoneMap[tempd][i];
            DFS(high, digits, cur);
        }
    }
    // 函数主体,最终执行时间低于100%用户
    vector<string> letterCombinations(string digits) {
        if (digits.empty()) {
            return combine;
        }
        DFS(digits.size(), digits, "");
        return combine;
    }
};

总结

DFS 通用模板在于快速的转换成树, 树的每次节点都是一次递归, 当你把问题转换成树的时候就能快速解决这个问题。

模板:

DFS( 出口, 分枝条, 节点)
{
    //出口
    {
        return
    }
    // 分枝操作
     for()
     {
          //节点计算
          DFS()
     }
}

这人写的好像比我好点…
这人

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值