diff --git a/include/s0077_combinations.hpp b/include/s0077_combinations.hpp new file mode 100644 index 0000000..4c59a11 --- /dev/null +++ b/include/s0077_combinations.hpp @@ -0,0 +1,15 @@ +#ifndef S0077_COMBINATIONS_HPP +#define S0077_COMBINATIONS_HPP + +#include +#include +#include + +using namespace std; + +class S0077 { + public: + vector> combine(int n, int k); +}; + +#endif diff --git a/notes/src/SUMMARY.md b/notes/src/SUMMARY.md index fb41751..5c267e7 100644 --- a/notes/src/SUMMARY.md +++ b/notes/src/SUMMARY.md @@ -38,6 +38,11 @@ - [遍历](./btree_iter.md) - [二叉搜索树](./bstree.md) +# 回溯 + +- [总结](./backtrack.md) +- [组合问题](./combinations.md) + # STL - [总结](./stl.md) diff --git a/notes/src/backtrack.md b/notes/src/backtrack.md new file mode 100644 index 0000000..7ba326f --- /dev/null +++ b/notes/src/backtrack.md @@ -0,0 +1,28 @@ +# 总结 + +使用场景: + +- 如果解决一个问题需要多个步骤,而每个步骤有多个可能的结果,题目又要求我们找出所有可能的结果,那么这个时候可以考虑回溯法。 +- 回溯法本质是在一棵树上进行深度优先遍历 + +算法设计: + +关键就是要学会分析和画图,然后确定传入参数。只要传入参数确定了代码框架就确定了 (返回值一般是 `void`): + +- 得有一个 `&result` 来存放结果 +- 每一层的节点所包含的数据是什么,需要一系列的参数来描述当前节点的状态(传入的参数实际上是上一轮迭代的数据)。 +- 终止条件是什么?回溯法中的每个节点并不是真的树状节点,没有 `nullptr` ,因此用空指针来判断是否到了叶子结点并不合理,需要其它的一些方法来确定是否到达叶子节点,比如高度。 +- 每次迭代要在上一次迭代的基础上进行哪些操作?需要哪些参数才能完成这些操作? + +复杂度分析: + +- 时间复杂度:最长路径长度 × 搜索树的节点数 +- 空间复杂度:一个节点所需要的空间 × 搜索树的节点数 + +分类: + +- 组合问题:N 个数里面按一定规则找出 k 个数的集合 +- 切割问题:一个字符串按一定规则有几种切割方式 +- 子集问题:一个 N 个数的集合里有多少符合条件的子集 +- 排列问题:N 个数按一定规则全排列,有几种排列方式 +- 棋盘问题:N 皇后,解数独等等 diff --git a/notes/src/combinations.md b/notes/src/combinations.md new file mode 100644 index 0000000..20416fd --- /dev/null +++ b/notes/src/combinations.md @@ -0,0 +1,19 @@ +# 组合问题 + +## [77. 组合](https://leetcode.cn/problems/combinations/description/) + +![combinations](https://paste.sainnhe.dev/Cytj.png) + +每个节点存储的数据是什么?是一个 `vector` 类型的数据,代表当前节点的路径。 + +下一个节点的路径需要基于上一个节点的路径来获得,因此传入参数应该有一个 `vector path`。另外,还需要有一个 `vector> &result` 用来存放结果。 + +终止条件是什么?回溯法中的每个节点并不是真的树状节点,没有 `nullptr` ,因此用空指针来判断是否到了叶子节点并不合理。 + +本题中我们可以通过高度来判断是否达到了叶子节点,如果 `path.size() == k` 则说明到达了叶子节点,则停止迭代,并把当前路径添加到结果变量中。 + +因此我们还需要高度 `k`,`int k` 也应该是一个传入参数。 + +为了防止重复,我们需要在 `[1, n]` 中的一个子区间 `[begin, n]` 中选择一个数,`[1, begin]` 是我们已经选过了的,因此我们需要 `int n` 和 `int begin` 来作为传入参数。 + +在每次迭代中,我们从 `[begin, n]` 中挨个选一个数加到上一轮迭代传递进来的 `path` 中,然后进行下一轮迭代。 diff --git a/src/s0077_combinations.cpp b/src/s0077_combinations.cpp new file mode 100644 index 0000000..f30bb06 --- /dev/null +++ b/src/s0077_combinations.cpp @@ -0,0 +1,27 @@ +#include "s0077_combinations.hpp" + +void combineDFS(int n, int k, int begin, vector &path, + vector> &result) { + // 当 path 长度等于 k 时停止迭代,并将加入结果 + if (path.size() == k) { + result.push_back(path); + return; + } + + // 遍历可能的搜索起点 + for (int i = begin; i <= n; ++i) { + // 将 i 加入路径 + path.push_back(i); + // 下一轮搜索 + combineDFS(n, k, i + 1, path, result); + // 回溯,撤销处理的节点 + path.pop_back(); + } +} + +vector> S0077::combine(int n, int k) { + vector> result; + vector path; + combineDFS(n, k, 1, path, result); + return result; +}