2.6 KiB
2.6 KiB
组合问题
77. 组合
每个节点存储的数据是什么?是一个 vector<int>
类型的数据,代表当前节点的路径。
下一个节点的路径需要基于上一个节点的路径来获得,因此传入参数应该有一个 vector<int> path
。另外,还需要有一个 vector<vector<int>> &result
用来存放结果。
终止条件是什么?回溯法中的每个节点并不是真的树状节点,没有 nullptr
,因此用空指针来判断是否到了叶子节点并不合理。
本题中我们可以通过高度来判断是否达到了叶子节点,如果 path.size() == k
则说明到达了叶子节点,则停止迭代,并把当前路径添加到结果变量中。
因此我们还需要高度 k
,int k
也应该是一个传入参数。
为了防止重复,我们需要在 [1, n]
中的一个子区间 [begin, n]
中选择一个数,[1, begin]
是我们已经选过了的,因此我们需要 int n
和 int begin
来作为传入参数。
在每次迭代中,我们从 [begin, n]
中挨个选一个数加到上一轮迭代传递进来的 path
中,然后进行下一轮迭代。
void combineDFS(int n, int k, int begin, vector<int> &path,
vector<vector<int>> &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();
}
}
我们现在来看看能不能优化。
在上图的这种情况中,每一层其实都可以剪掉一些不可能的分支,我们可以对每一层循环的终止条件进行限制,从而剪枝。
优化后的代码如下:
void combineDFS(int n, int k, int begin, vector<int> &path,
vector<vector<int>> &result) {
// 当 path 长度等于 k 时停止迭代,并将加入结果
if (path.size() == k) {
result.push_back(path);
return;
}
// 遍历可能的搜索起点
// 在这一步中,每一次循环都可以对末尾进行限制来剪枝
for (int i = begin; i <= n - (k - path.size()) + 1; ++i) {
// 将 i 加入路径
path.push_back(i);
// 下一轮搜索
combineDFS(n, k, i + 1, path, result);
// 回溯,撤销处理的节点
path.pop_back();
}
}