题目描述
给定两个整数 n
和 k
,返回范围 [1, n]
中所有可能的 k
个数的组合。
你可以按 任何顺序 返回答案。
示例 1:
输入:n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
示例 2:
输入:n = 1, k = 1
输出:[[1]]
提示:
1 <= n <= 20
1 <= k <= n
思路
回溯法三部曲:
- 确定递归函数的参数和返回值。定义两个全局变量,一个用来存放符合条件单一结果,一个用来存放符合条件结果的集合。函数里一定有两个参数,既然是集合n里面取k个数,那么n和k是两个int型的参数,然后还需要一个参数,为int型变量startIndex,这个参数用来记录本层递归的中,集合从哪里开始遍历(集合就是[1,...,n] )。
- 确定终止条件。path数组的大小如果达到k,说明已经找到了一个子集大小为k的组合。
- 确定单层递归的逻辑。for循环每次从startIndex开始遍历,然后用path保存取到的节点i,让递归函数通过不断调用自己一直往深处遍历,然后回溯,撤销本次处理的结果。
可以剪枝的地方就在递归中每一层的for循环所选择的起始位置,如果for循环选择的起始位置之后的元素个数已经不足需要的元素个数了,那么就没有必要搜索了。
优化过程如下:
-
已经选择的元素个数:path.size();
-
所需需要的元素个数为: k - path.size();
-
列表中剩余元素(n-i) >= 所需需要的元素个数(k - path.size())
-
在集合n中至多要从该起始位置 : i <= n - (k - path.size()) + 1,开始遍历
代码
C++版:
回溯法,未优化版
class Solution {
public:
// 回溯法,未优化版
vector<vector<int> > result; // 存放符合条件结果的集合
vector<int> path; // 存放符合条件的结果
// startIndex是下一层递归的起始位置,防止出现重复的组合
void backtracking(int n,int k,int startIndex){
// 终止条件
if(path.size()==k){
result.push_back(path);
return ;
}
for(int i=startIndex;i <= n;i++){
path.push_back(i); // 处理节点
backtracking(n,k,i+1);
path.pop_back(); // 回溯,撤销处理的节点
}
}
vector<vector<int>> combine(int n, int k) {
backtracking(n, k, 1);
return result;
}
};
回溯法,优化版
class Solution {
public:
// 回溯法,优化版
vector<vector<int> > result; // 存放符合条件结果的集合
vector<int> path; // 存放符合条件的结果
// startIndex是下一层递归的起始位置,防止出现重复的组合
void backtracking(int n,int k,int startIndex){
// 终止条件
if(path.size()==k){
result.push_back(path);
return ;
}
for(int i=startIndex;i <= n - (k - path.size()) + 1;i++){ // 剪枝优化
path.push_back(i); // 处理节点
backtracking(n,k,i+1);
path.pop_back(); // 回溯,撤销处理的节点
}
}
vector<vector<int>> combine(int n, int k) {
backtracking(n, k, 1);
return result;
}
};
Python版:
class Solution:
# 回溯法,优化版
def backtracking(self, n, k, startIndex, path, result):
if len(path) == k:
result.append(path[:])
return
for i in range(startIndex, n - (k - len(path)) + 2): # 剪枝优化
path.append(i) # 处理节点
self.backtracking(n, k, i + 1, path, result)
path.pop() # 回溯,撤销处理的节点
def combine(self, n: int, k: int) -> List[List[int]]:
result = [] # 存放结果集
self.backtracking(n, k, 1, [], result)
return result
需要注意的地方
1.回溯法的搜索过程可以抽象为树形结构,这样可以直观的看出搜索的过程。
2.回溯法中可以剪枝的地方就在递归中每一层的for循环所选择的起始位置。