题目来源:. - 力扣(LeetCode)
题目思路分析
题目描述:combinationSum3
问题要求从 1 到 9 的整数中找出所有可能的 k 个数的组合,使得这些数的和等于给定的整数 n。注意,这里的每个数字只能使用一次,且组合中的数字需要是唯一的。
解题思路:
- 回溯算法:这是一个典型的回溯问题,我们需要尝试所有可能的组合,并在每一步中检查是否满足条件(组合的长度为 k 且和为 n)。
- 递归调用:通过递归调用,我们可以逐步构建当前的组合,并在每一步中决定是否继续添加下一个数字。
- 剪枝:如果当前组合的长度已经达到 k 或当前和已经小于 n(因为我们是从大到小遍历数字,所以一旦和小于 n,后续无论如何都无法使和等于 n),我们可以提前终止当前分支的搜索。
- 逆序遍历:由于我们需要从 1 到 9 中选择数字,且每个数字只能使用一次,因此我们可以选择逆序遍历这些数字。这样做的好处是,在回溯过程中,我们可以更容易地剪枝,因为一旦当前和小于 n,后续的数字(由于它们是更小的数字)也无法使和达到 n。
代码:
#include <vector> class Solution {
public: // 回溯函数 void Backtracking(vector<vector<int>>& ans, // 存储所有有效组合的二维向量 vector<int>& sum, // 当前正在构建的组合 int k, int n, // 目标组合的长度和目标总和 int index) { // 当前尝试的数字(从大到小遍历) // 如果当前组合长度等于k且总和等于n,则找到一个有效组合 if (sum.size() == k && n == 0) { ans.push_back(sum); return; } // 如果组合长度达到k或总和已经小于n,则无需继续探索 if (sum.size() == k || n < 0) { return; } // 逆序遍历从 index 到 1 的所有数字(注意这里 index 初始化为 10,是为了方便从 9 开始遍历) for (index -= 1; index >= 1; --index) { n -= index; // 将当前数字从目标和中减去 sum.push_back(index); // 将当前数字加到当前组合中 Backtracking(ans, sum, k, n, index); // 递归调用,但注意 index 不再变化,因为我们是在遍历 n += index; // 回溯:将当前数字加回到目标和中 sum.pop_back(); // 回溯:从当前组合中移除当前数字 } } // 主函数 vector<vector<int>> combinationSum3(int k, int n) { vector<vector<int>> ans; // 存储所有有效组合的二维向量 vector<int> sum; // 当前正在构建的组合 Backtracking(ans, sum, k, n, 10); // 从 10 开始(实际上从 9 开始遍历,因为 index-=1) return ans; }
};
注释详解:
Backtracking
函数是回溯算法的核心,它尝试构建所有可能的组合。ans
存储所有有效的组合。sum
存储当前正在构建的组合。k
和n
分别是目标组合的长度和目标总和。index
是当前尝试的数字(从大到小遍历)。- 在
Backtracking
函数中,我们首先检查是否找到了一个有效的组合(长度等于 k 且和为 n)。 - 然后,我们检查是否需要剪枝(组合长度达到 k 或和小于 n)。
- 接下来,我们逆序遍历从
index
到 1 的所有数字,并递归调用Backtracking
函数。 - 在回溯过程中,我们更新目标和与当前组合,并在递归调用后恢复它们的状态。
知识点摘要
- 回溯算法:一种通过构建候选解并逐步扩展直到满足问题要求或无法再扩展的算法。
- 递归调用:函数直接或间接调用自身,用于在回溯算法中逐步构建候选解。
- 剪枝:在搜索过程中提前终止不符合要求的候选解,以减少不必要的计算。
- 逆序遍历:在处理类似问题时,逆序遍历可以更方便地进行剪枝。
combinationSum3
问题是一个典型的回溯算法问题,通过递归调用和剪枝来找出所有满足条件的组合。在回溯过程中,我们逐步构建当前组合,并在每一步中检查是否满足条件。如果不满足条件,则回溯并尝试其他可能的数字。逆序遍历数字可以更方便地进行剪枝,从而提高算法的效率。通过理解回溯算法的基本原理和递归调用的方式,我们可以更好地解决类似的问题。