一道數字到字符串的題,將數字映射到手機的字符串上,是編程之美版本的簡略版,簡明的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選擇的混淆
但是如果弄個有重複數字的全排列,我就搞不清了,還有重複數字的組合數= =