回溯法总结
一道数字到字符串的题,将数字映射到手机的字符串上,是编程之美版本的简略版,简明的DFS,其实我更愿意用backtrack framework来做,说成是回溯法,本质没有区别
期间犯了一个错误,就是for里面多写了一个f(),结果导致out of memory等奇怪错误。 主要是受了candidate有两个情况,直接写成两个,外加用select bool数组将unmake 和下一轮的make重合在一起,于是下意识的多写了一个f() 而没有仔细思考。
其实一般框架都是一个for里面先逐个make candidate,然后递归,然后unmake
外加一道子集和问题,返回给定集合S 的所有子集,典型的回溯。其实感觉这个就是有模板的,但是如果情况复杂些,我还是改起来有点费劲= =
打印 C(n,k)的所有情况,里面多一个k,于是对应多一个selectk表示当前已经选了多少变量了,好像递归出口如果写成下面这个会有问题,
具体还没分析出为啥= =
C(n,k)代码
总结:回溯法其实就是你for循环要做,但是不知道多少层for循环的时候,或者是解指数(子集树)或者阶乘级(排列树),然后里面涉及的每个当前解的变量要么数组传递,递归调用共享访问,要么vector引用传递保持递归调用一致,要么全局变量。
然后记录全局变量的也是如此,保持全局一致,如果作为递归参数千万不要值传递,导致每个递归栈保存的都是一个copy而不是访问全局的一个。然后递归出口的时候一定要把情况罗列清楚,多个条件逻辑与或分析能力。
一道数字到字符串的题,将数字映射到手机的字符串上,是编程之美版本的简略版,简明的DFS,其实我更愿意用backtrack framework来做,说成是回溯法,本质没有区别
vector<string> numtoletter;
void f(vector<char>& strvec, int selectn, int n,string digits, vector<string> numtoletter, vector<string>& allcombinations)
{
if(selectn==n)
{
string str;
for(int i=0;i<n;i++)
str+=strvec.at(i);
allcombinations.push_back(str);
}
else
{
for(int i=0;i< numtoletter.at(digits.at(selectn)-'2').length();i++)// if 1?
{
strvec.push_back(numtoletter.at(digits.at(selectn)-'2').at(i));
f(strvec,selectn+1,n,digits,numtoletter,allcombinations);
strvec.pop_back();
//f(strvec,selectn+1,n,digits,numtoletter,allcombinations);
}
}
}
vector<string> letterCombinations(string digits) {
numtoletter.push_back("abc");
numtoletter.push_back("def");
numtoletter.push_back("ghi");
numtoletter.push_back("jkl");
numtoletter.push_back("mno");
numtoletter.push_back("pqrs");
numtoletter.push_back("tuv");
numtoletter.push_back("wxyz");
vector<char> strvec;
vector<string> allcombinations;
f(strvec,0,digits.size(),digits,numtoletter,allcombinations);
return allcombinations;
}
期间犯了一个错误,就是for里面多写了一个f(),结果导致out of memory等奇怪错误。 主要是受了candidate有两个情况,直接写成两个,外加用select bool数组将unmake 和下一轮的make重合在一起,于是下意识的多写了一个f() 而没有仔细思考。
其实一般框架都是一个for里面先逐个make candidate,然后递归,然后unmake
外加一道子集和问题,返回给定集合S 的所有子集,典型的回溯。其实感觉这个就是有模板的,但是如果情况复杂些,我还是改起来有点费劲= =
void subsets_recur(bool *select, vector<int> S, vector<vector<int>>&intvec, int selectn, int n)
{
if(selectn==n)
{
vector<int> vec;
for(int i=0;i<S.size();i++)
{
if(select[i]==true)
vec.push_back(S.at(i));
}
intvec.push_back(vec);
}
else
{
select[selectn]=false;//not select
subsets_recur(select,S,intvec,selectn+1,n);
select[selectn]=true;
subsets_recur(select,S,intvec,selectn+1,n);
}
}
vector<vector<int> > subsets(vector<int> &S) {
bool *select=new bool[S.size()];
vector<vector<int>> intvec;
sort(S.begin(),S.end());
subsets_recur(select,S,intvec,0,S.size());
delete[] select;
return intvec;
}
打印 C(n,k)的所有情况,里面多一个k,于是对应多一个selectk表示当前已经选了多少变量了,好像递归出口如果写成下面这个会有问题,
具体还没分析出为啥= =
if(selectk==k)
{
string str;
for(int i=0;i<n;i++)
if(select[i]==true)
str+='1';
else
str+='0';
//cout<<endl;
numset.push_back(str);
}
else if(selectn==n)
;
else
{
select[selectn]=false;
SubSetNum(numset,select,selectk,k,selectn+1,n);
select[selectn]=true;
SubSetNum(numset,select,selectk+1,k,selectn+1,n);
}
C(n,k)代码
void Combinations(bool* select, vector<vector<int>> &vecset,int selectk, int k, int selectn, int n)
{
if(k>n) return ;
if(selectn==n)
{
if(selectk==k)//proning
{
vector<int> vec;
for(int i=0;i<n;i++)
{
if(select[i]==true)
vec.push_back(i+1);
}
vecset.push_back(vec);
}
}
else
{
select[selectn]=false;
Combinations(select, vecset,selectk,k,selectn+1,n);
select[selectn]=true;
Combinations(select, vecset,selectk+1,k,selectn+1,n);
}
}
vector<vector<int> > combine(int n, int k)
{
bool* select=new bool[n];
memset(select,0,sizeof(bool)*n);
vector<vector<int>> vecset;
Combinations(select,vecset,0,k,0,n);
delete [] select;
return vecset;
}
总结:回溯法其实就是你for循环要做,但是不知道多少层for循环的时候,或者是解指数(子集树)或者阶乘级(排列树),然后里面涉及的每个当前解的变量要么数组传递,递归调用共享访问,要么vector引用传递保持递归调用一致,要么全局变量。
然后记录全局变量的也是如此,保持全局一致,如果作为递归参数千万不要值传递,导致每个递归栈保存的都是一个copy而不是访问全局的一个。然后递归出口的时候一定要把情况罗列清楚,多个条件逻辑与或分析能力。
本质是for遍历每个数,make, recursive, unmake 不要多加recursive导致和 01选择的混淆
但是如果弄个有重复数字的全排列,我就搞不清了,还有重复数字的组合数= =