回溯是一个常见的算法,类似于深搜/广搜,会穷举每一个可能。但是会有一个恢复选择的操作。
算法核心框架如下:
for 选择 in 选择列表:
#做选择
将该选择从选择列表里移除
路径.add(选择)
backtrace(路径,选择列表)
#撤销选择
路径.remove(选择)
将该选项恢复到选择列表
经典例题
class Solution {
public:
void backtrack(string& digits,vector<string>& ret ,string curstr,int curdeepth){
//边界判断
if(curdeepth==digits.size()){
if(!curstr.empty())
ret.push_back(curstr);
return;
}
//选择路径
for(int i=0;i<mapString[digits[curdeepth]-'0'].size();++i){
curstr += mapString[digits[curdeepth]-'0'][i];
backtrack(digits,ret,curstr,curdeepth+1);
curstr.pop_back();//尾删,撤销选择,向上回溯
}
}
vector<string> letterCombinations(string digits) {
vector<string> ret;
backtrack(digits,ret,"",0);
return ret;
}
private:
vector<string> mapString = {"", "", "abc", "def", "ghi", "jkl", "mno","pqrs", "tuv", "wxyz"};
};
DFS+回溯可解。
类似于上一题,这里我用了一个deepth变量来控制决策树的层数。
class Solution {
public:
void backtrace(vector<int>& candidates,vector<vector<int>>&ret, vector<int>&tmp,int sum,int target,int deepth){
//边界判断
if(sum>target) return;
if(sum==target){
ret.push_back(tmp);
return;
}
//搜索
for(int i=deepth;i<candidates.size();++i){
tmp.push_back(candidates[i]); //选择
//进入下一层决策树
backtrace(candidates,ret,tmp,sum+candidates[i],target,deepth++);
//取消本次选择
tmp.pop_back();
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<vector<int>> ret;
vector<int> tmp; //记录路径
int sum=0;
int deepth=0;
backtrace(candidates,ret,tmp,sum,target,deepth);
return ret;
}
};
class Solution {
public:
void backtrace(string& tiles,unordered_set<string>& dict,string & curstr,vector<int>& usedid){
if(dict.count(curstr)==0 && curstr.size()!=0){
dict.insert(curstr);
}
for(int i=0;i<tiles.size();++i){
//已使用过的字符则跳过
if(usedid[i]==1)
continue;
usedid[i]=1;
//遍历下一层
string tmp=curstr + tiles[i];
backtrace(tiles,dict,tmp,usedid);
//回溯
tmp.pop_back();
usedid[i]=0;
}
}
int numTilePossibilities(string tiles) {
unordered_set<string> dict; //方便查重
vector<int> usedid(tiles.size(),0); //记录被访问过的字符
string curstr;
backtrace(tiles,dict,curstr,usedid);
return dict.size();
}
};