77. 组合
题目描述
给定两个整数 n
和 k
,返回范围 [1, n]
中所有可能的 k
个数的组合。
你可以按 任何顺序 返回答案。
题目分析
先放上回溯模板:
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
我们用vector<int> vec
来存放每一个答案,vector<vector<int>> result
来存放全部的答案。
那么终止条件就是:当vec
中的元素等于题目规定的k
个时,就可以把当前vec
存放到result
中了。也就是:
if(vec.size() == k){
result.push_back(vec);
return;
}
整体范围为[1, n]
的集合,而我们在进行递归时需要选择该层处理的元素子集,也就是范围是[j, n]
的子集,1<=j<=n。
for(int i = j; i <= n; i++){
vec.push_back(i);
backtracking(i+1, k, n, vec, result);
vec.pop_back();
}
其实看下面这张图,就能理解回溯的逻辑:
pop操作实际上可以看作:例如从结果集合[1, 2]
返回到上层的在2,3,4中取一个数的[2, 3, 4]
这个集合中在进行处理,也就是进行取3操作。
整体cpp代码
class Solution {
public:
void backtracking(int j, int k, int n, vector<int>& vec, vector<vector<int>>& result){
if(vec.size() == k){
result.push_back(vec);
return;
}
for(int i = j; i <= n; i++){
vec.push_back(i);
backtracking(i+1, k, n, vec, result);
vec.pop_back();
}
}
vector<vector<int>> combine(int n, int k) {
vector<int> vec;
vector<vector<int>> result;
backtracking(1, k, n, vec, result);
return result;
}
};
216. 组合总和 III
题目描述
找出所有相加之和为 n 的 k 个数的组合,且满足下列条件:
- 只使用数字1到9
- 每个数字 最多使用一次
返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。
题目分析
其实和上面一样,只不过多了一个总和为n。
cpp代码
class Solution {
public:
void backtracking(int m, int k, int n, vector<int>& vec, vector<vector<int>>& result){
// 如果当前路径元素个数为k,且和为n,则放入答案集
if(vec.size() == k){
if(accumulate(vec.begin(), vec.end(), 0) == n){
result.push_back(vec);
}
return;
}
// 从m开始处理
for(int i = m; i <= 9; i++){
vec.push_back(i); // 将当前元素放入路径
backtracking(i+1, k, n, vec, result);
vec.pop_back(); // 回溯
}
}
vector<vector<int>> combinationSum3(int k, int n) {
vector<int> vec;
vector<vector<int>> result;
backtracking(1, k, n, vec ,result);
return result;
}
};
17. 电话号码的字母组合
题目描述
给定一个仅包含数字 2-9 的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
题目分析
这里涉及到在多个集合中进行组合。
通过画图来分析,例如给定数字串string digits = "23"
,我们建立对应的字母集vector<string> digits_to_abc = ["abd", "def"]
。
- 我们首先从
'2'
对应的字母集"abc"
中选取一个字母; - 然后再在
'3'
对应的字母集"def"
中选取一个字母; - 每次只选一个字母,并且每个数字的字母集都要进行选取操作。那么我们一条路径走完就是要在每个字母集中选取完,这里我们设置一个
int index
来表示当前应该选取的字母集,那么路径完成的判断语句可以写成:
if(index == digits_to_abc.size()){
results.push_back(s);
return;
}
一条路径string s
走完后,我们将当前记录的路径放到最终的结果vector<string> results
中。
那么如何进行回溯呢?
我们拿最左边"ad"
这条路径作为例子来分析:
我们当前路径是s = "ad"
,通过判断发现index已经指向了最后一个字母集,没有字母集可以选择了,那这个时候我们就要回到上一个字母集去选择另一个字母(另一条路径),那么就要把当前路径中在最后一个字母集选取的字母pop出来,这样就完成了回溯。
cpp代码
class Solution {
public:
// 回溯的参数包括:我们根据输入digits建好的字母数组,vec是每一层回溯的结果,results放置所有答案
void backtracking(int index, vector<string> digits_to_abc, string& s, vector<string>& results){
if(index == digits_to_abc.size()) {
results.push_back(s);
return;
}
string string_letter = digits_to_abc[index];
for(int i = 0; i < string_letter.size(); i++){
s += string_letter[i];
backtracking(index + 1, digits_to_abc, s, results);
s.pop_back();
}
}
vector<string> letterCombinations(string digits) {
vector<string> results;
if(digits.size() == 0) return results;
cout << "done1" << endl;
// 先构造每个数字对应的字母组合
vector<string> temp;
temp.push_back("abc");
temp.push_back("def");
temp.push_back("ghi");
temp.push_back("jkl");
temp.push_back("mno");
temp.push_back("pqrs");
temp.push_back("tuv");
temp.push_back("wxyz");
cout << "done2" << endl;
// 建一个string的vector来存放digits对应的字母
vector<string> digits_to_abc;
for(char c : digits){
digits_to_abc.push_back(temp[c-'2']);
}
// 打印看看是不是对应的
cout << "输入digits:" << digits << endl;
for(string s : digits_to_abc){
cout << s << endl;
}
// 进行回溯
string s;
backtracking(0, digits_to_abc, s, results);
return results;
}
};