回溯 DFS 算法深入浅出,一文吃透!
原文同步在:https://github.com/EricPengShuai/Interview/blob/main/algorithm/回溯算法.md
主要参考的是 liweiwei 的总结
回溯法 采用试错的思想,它尝试分步的去解决一个问题。在分步解决问题的过程中,当它通过尝试发现现有的分步答案不能得到有效的正确的解答的时候,它将取消上一步甚至是上几步的计算,再通过其它的可能的分步解答再次尝试寻找问题的答案。回溯法通常用最简单的 递归 方法来实现,在反复重复上述的步骤后可能出现两种情况:
深度优先搜索 算法(Depth-First-Search,DFS)是一种用于 遍历或搜索树或图 的算法。这个算法会 尽可能深 的搜索树的分支。当结点 v 的所在边都己被探寻过,搜索将 回溯 到发现结点 v 的那条边的起始结点。这一过程一直进行到已发现从源结点可达的所有结点为止。如果还存在未被发现的结点,则选择其中一个作为源结点并重复以上过程,整个进程反复进行直到所有结点都被访问为止。
典型的 DFS 但是没有回溯的题目:17. 电话号码的字母组合
简单比较
「回溯算法」与「深度优先遍历」都有**「不撞南墙不回头」**的意思。「回溯算法」强调了「深度优先遍历」思想的用途,用一个 不断变化 的变量,在尝试各种可能的过程中,搜索需要的结果。
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案
这是一到回溯算法的入门题,很好的说明了回溯算法在选择回退时的用法,几个比较重要的点是:
class Solution {
vector<vector<int>> res;
public:
vector<vector<int>> permute(vector<int>& nums) {
vector<int> path;
vector<int> used(nums.size(), 0);
backtrace(path, used, nums, 0, nums.size());
return res;
}
void backtrace(vector<int>& path, vector<int>& used, vector<int>& nums, int dep, int n) {
if (dep == n) {
res.push_back(path);
return;
}
for (int i = 0; i < n; ++ i) {
if (used[i]) continue;
path.push_back(nums[i]);
used[i] = 1;
backtrace(path, used, nums, dep+1, n);
used[i] = 0; // 回溯
path.pop_back();
}
}
};
另外在 C++ 里,最好还是不要写
vector
,因为vector
返回的是一个std::vector
的对象,数据量大时比::reference vector
要慢,参考
有重复元素的全排列(LC.47)
nums[i-1]==nums[i]
判重组合问题的排列(LC.39)
有一个小经验就是 used 数组 和 begin 变量 一般不用一起使用
排列型回溯
题目 | 说明 | 题解 |
---|---|---|
46. 全排列 | 回溯入门问题,无重复元素的排列问题 | 通过 |
47. 全排列 II | 去重是关键,排序比较,上一个相同的元素撤销之后再剪枝 | 剪枝图 |
组合型回溯
题目 | 说明 | 题解 |
---|---|---|
39. 组合总和 | 组合问题需要按照某种顺序搜索:每一次搜索的时候设置 下一轮搜索的起点 begin ,也可以排序之后加速剪枝 |
通过 |
40. 组合总和 II | 和 LC.39 区别是这个有重复元素,需要去掉当前层第二个数值重复的节点 | 剪枝 |
216. 组合总和 III | 和 LC.40 类似,这题没有重复元素,两个剪枝:小于最小的 || 大于最大的 | 0x3F |
77. 组合 | 和 LC.39 类似,按照 begin 为起点遍历然后回溯就可以,注意不能重复 | 通过 |
子集型回溯
题目 | 说明 | 题解 |
---|---|---|
78. 子集 | 和 LC.39 类似,按照 begin 为起点遍历回溯就可以 | 通过 |
90. 子集 II | 在 LC.78 的基础上有重复元素的考虑,和 LC.40 类似的剪枝 | 通过 |
131. 分割回文串 | 枚举字符之间的逗号,按照 idx 顺序回溯,判断回文 | 通过 |
698. 划分为k个相等的子集 | 抽象成 k 个桶,每个桶的容量为子集和大小 | 通过 |
473. 火柴拼正方形 | 和 LC.698 一模一样,抽象成 4 个桶 | 通过 |
2305. 公平分发饼干 | k 个桶,但是桶大小未知,从大到小DFS回溯 | 通过 |
854. 相似度为 K 的字符串 | DFS 回溯,剪枝,有点难… | 通过 |
https://github.com/EricPengShuai/Interview
如果觉得不错的话可以 ⭐️ 一下