三步完成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
著作权归领扣网络所有。商业转载请联系官方授权,非商业转载请注明出处。
我们可以自然的联想到解决该问题的步骤:
二、三步完成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()
}
}
这人写的好像比我好点…
这人