216.组合总和III
一、做题感受&第一想法
直接套用“组合”问题的回溯框架,在找到符合规格的组合后判断它们的和是否符合题目。如果符合则记录,不符合则直接返回。
但是没有剪枝。
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
int sum(vector<int> path){ //计算path中元素之和
int sum = 0;
for(int i = 0;i < path.size();i++){
sum += path[i];
}
return sum;
}
vector<vector<int>> combinationSum3(int k, int n) {
backtracking(k,n,1);
return result;
}
void backtracking(int k, int n, int startIndex){
if(path.size() == k){ //已经找到size为k的组合
if(sum(path) == n) result.push_back(path); //如果元素之和为n,则符合题意,收集结果
return; //如果元素之和不为n,返回。
}
for(int i = startIndex; i <= 9; i++){
path.push_back(i);
backtracking(k,n,i+1);
path.pop_back();
}
return;
}
};
二、学习文章后收获
1.剪枝的代码
- for循环的范围,可以剪枝
- 当path.size()还未到达n,但sum已经大于k时,可以剪枝
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
vector<vector<int>> combinationSum3(int k, int n) {
int sum = 0;
backtracking(k,n,sum,1);
return result;
}
void backtracking(int k, int n, int sum, int startIndex){
if(path.size() == k && sum == n){
result.push_back(path);
return;
}
else if(path.size() == k && sum != n){
return;
}
else if(sum > n){ //剪枝
return;
}
for(int i = startIndex; i <= 9 - (k - path.size()) + 1 ; i++){ //剪枝
path.push_back(i);
sum += i;
backtracking(k,n,sum,i+1);
sum -= i;
path.pop_back();
}
return;
}
};
三、过程中遇到的问题
1.回溯操作要做完整
要观察清楚哪些变量需要回溯。
下面代码中,sum没有被回溯,导致错误。
for(int i = startIndex; i <= 9 - (k - path.size()) + 1 ; i++){ //剪枝
path.push_back(i);
sum += i;
backtracking(k,n,sum,i+1);
//没有回溯sum!!!
path.pop_back();
}
2.时间、空间复杂度
时间:O(2^n)
空间:O(n)
???why???
17.电话号码的字母组合
一、做题感受&第一想法
一开始理解错了题意。我以为是:给出从2-9的某个范围,然后返回所有可能的数字排列对应的所有可能字母排列。
实际上是指,在给定的数字序列中,返回所有可能的字母排列。
分析回溯三要素:
- 函数参数:一定要能标明该层回溯的“集合”范围! 本题中,给出数字序号
index
,即可根据字符字典dictionary
找到对应的字符集合 - 层内逻辑:用for循环遍历数字对应的字符集合,从而“开枝散叶”。
- 返回条件:当
str
长度等于数字串长度,则收集结果(注意数字串长度为0、str为空的情况,不要收集。)(或者,通过main函数控制进入回溯的数字串一定不为空)
class Solution {
public:
vector<int> digits_int;
vector<string> result;
vector<string> dictionary;
string str;
void initDict(){
dictionary.push_back(" ");
dictionary.push_back(" ");
dictionary.push_back("abc");
dictionary.push_back("def");
dictionary.push_back("ghi");
dictionary.push_back("jkl");
dictionary.push_back("mno");
dictionary.push_back("pqrs");
dictionary.push_back("tuv");
dictionary.push_back("wxyz");
return;
}
vector<string> letterCombinations(string digits) {
initDict();
for(int i = 0; i < digits.size();i++){
digits_int.push_back(digits[i]-'0');
}
backtracking(0);
return result;
}
void backtracking(int index){ //index是本层处理的数字的序号
if(index == digits_int.size()){
if(!str.empty()) result.push_back(str);
return;
}
for(int i = 0;i < dictionary[digits_int[index]].size();i++){
int size = str.size();
str += dictionary[digits_int[index]][i];
backtracking(index + 1);
str.resize(size);
}
return;
}
};
二、学习文章后收获
1.面对电话号码的字母组合问题,如何存储“字典”
//方法一:string数组
string letter[10] = { //存储映射关系
"",
"",
"abc",
"def",
"ghi",
"jkl",
"mno",
"pqrs",
"tuv",
"wxyz"
};
//方法二:vector<string>
vector<string> dictionary; //字典
void initDict(){
dictionary.push_back(" ");
dictionary.push_back(" ");
dictionary.push_back("abc");
dictionary.push_back("def");
dictionary.push_back("ghi");
dictionary.push_back("jkl");
dictionary.push_back("mno");
dictionary.push_back("pqrs");
dictionary.push_back("tuv");
dictionary.push_back("wxyz");
return;
}
2.代码随想录上代码(可以跳过,基本思路一样)
class Solution {
public:
string letter[10] = { //存储映射关系
"",
"",
"abc",
"def",
"ghi",
"jkl",
"mno",
"pqrs",
"tuv",
"wxyz"
};
vector<string> result;
string str;
vector<string> letterCombinations(string digits) {
if(digits.size()) backtracking(0,digits);
return result;
}
void backtracking(int index, string digits){
if(index == digits.size()){
result.push_back(str);
return;
}
int digit = digits[index] - '0';
string strTemp= letter[digit];
for(int i = 0;i < strTemp.size();i++){
str += strTemp[i];
backtracking(index + 1, digits);
str.pop_back();
}
return;
}
};
3.一些思考
本题和”组合“问题的区别??
- 组合问题:在同一个集合中求组合
- 本题:针对每一个数字有不同集合,也就是求不同集合间的组合。
4.面试中尽量考虑边界条件
注意:输入1 * #按键等等异常情况(可通过字符转换为数字时,限制ASIIC码的范围)
代码中最好考虑这些异常情况,但题目的测试数据中应该没有异常情况的数据,所以我就没有加了。
但是要知道会有这些异常,如果是现场面试中,一定要考虑到!
5.时间、空间复杂度
时间:O(3n * 4m)
空间:O(3n * 4m)
6.如果把回溯过程放入递归函数里
vector<string> result;
vector<string> letterCombinations(string digits) {
string str;
if(digits.size()) backtracking(0,digits,str);
return result;
}
void backtracking(int index, string digits,string str){ //还要传入str
if(index == digits.size()){
result.push_back(str);
return;
}
int digit = digits[index] - '0';
string strTemp= letter[digit];
for(int i = 0;i < strTemp.size();i++){
backtracking(index + 1, digits, str + strTemp[i]); //注意这里的不同!!
//之后不用处理str,因为str没变
}
return;
}
};