leetcode/notes/src/combinations.md
Sainnhe Park 0d6a3a7513
Some checks failed
ci/woodpecker/push/test Pipeline failed
s0040
2023-02-02 11:33:45 +08:00

3.5 KiB
Raw Blame History

组合问题

77. 组合

combinations

每个节点存储的数据是什么?是一个 vector<int> 类型的数据,代表当前节点的路径。

下一个节点的路径需要基于上一个节点的路径来获得,因此传入参数应该有一个 vector<int> path。另外,还需要有一个 vector<vector<int>> &result 用来存放结果。

终止条件是什么?回溯法中的每个节点并不是真的树状节点,没有 nullptr ,因此用空指针来判断是否到了叶子节点并不合理。

本题中我们可以通过高度来判断是否达到了叶子节点,如果 path.size() == k 则说明到达了叶子节点,则停止迭代,并把当前路径添加到结果变量中。

因此我们还需要高度 kint k 也应该是一个传入参数。

为了防止重复,我们需要在 [1, n] 中的一个子区间 [begin, n] 中选择一个数,[1, begin] 是我们已经选过了的,因此我们需要 int nint 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();
  }
}

我们现在来看看能不能优化。

optimization

在上图的这种情况中,每一层其实都可以剪掉一些不可能的分支,我们可以对每一层循环的终止条件进行限制,从而剪枝。

优化后的代码如下:

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();
  }
}

216. 组合总和 III

39. 组合总和

40. 组合总和 II

最难的一个组合总和,因为 candidates 有重复元素,而要求最终结果不能重复。

e.g. 1

Input: candidates = [10,1,2,7,6,1,5], target = 8
Output:
[
[1,1,6],
[1,2,5],
[1,7],
[2,6]
]

如果你只是单纯地在 s0039 的基础上在下一次递归中将 startIndex 设为 i + 1 那么最终结果就会出现两个 [1, 2, 5]

如果你直接排除 candidates[i] == candidates[i - 1] 的情形,那么最终结果就没有 [1, 1, 6]

正确的逻辑应该是如果 candidates[i] == candidates[i - 1]candidates[i - 1] 使用过,则剪枝。

怎么判断 candidates[i - 1] 是否使用过呢?我们创建一个 vector<bool> used 用来记录每个元素是否使用过。