void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
// for循环-横向遍历
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归-纵向遍历
回溯,撤销处理结果
}
}
class Solution {
public:
vector<int>path;// 选取的组合
vector<vector<int>>ret;// 最终结果
// 用startIndex来记录下一层递归搜索的起始位置
// 防止重现重复的组合
void backtracking(int n ,int k ,int startIndex){
if(path.size() == k){
ret.push_back(path);// 收集结果
return ;
}
for(int i = startIndex;i <= n;i++){// 横向遍历
path.push_back(i);// 添加
backtracking(n,k,i+1);// 纵向遍历
path.pop_back();// 撤销
}
}
vector<vector<int>> combine(int n, int k) {
backtracking(n,k,1);
return ret;
}
};
class Solution {
public:
vector<int>path;// 选取的组合
vector<vector<int>>ret;// 最终结果
// 用startIndex来记录下一层递归搜索的起始位置
// 防止重现重复的组合
void backtracking(int n ,int k ,int startIndex){
if(path.siz)e() == k){
ret.push_back(path);// 收集结果
return ;
}
// 如果for循环选择的起始位置之后的元素个数 已经不足 我们需要的元素个数了,那么就没有必要搜索了。
for (int i = startIndex; i <= n - (k - path.size()) + 1; i++){ // i为本次搜索的起始位置
path.push_back(i);// 添加
backtracking(n,k,i+1);// 纵向遍历
path.pop_back();// 撤销
}
}
vector<vector<int>> combine(int n, int k) {
backtracking(n,k,1);
return ret;
}
};
class Solution {
public:
vector<int>path;
vector<vector<int>>ret;
void backtracking(int k, int n,int startIndex){
if(path.size() == k){
// 满足指定和才收集
if(accumulate(path.begin(),path.end(),0) == n)ret.push_back(path);
return ;
}
for(int i = startIndex;i <= 9;i++){
path.push_back(i);
backtracking(k,n,i+1);
path.pop_back();
}
}
vector<vector<int>> combinationSum3(int k, int n) {
backtracking(k,n,1);
return ret;
}
};
class Solution {
public:
vector<int>path;
vector<vector<int>>ret;
void backtracking(int k, int n,int startIndex){
if(path.size() == k){
// 满足指定和才收集
if(accumulate(path.begin(),path.end(),0) == n)ret.push_back(path);
return ;
}
for(int i = startIndex;i <= 9-(k-path.size())+1;i++){// 剩余元素个数不足了,终止
path.push_back(i);
if(accumulate(path.begin(),path.end(),0) > n){// 已经大于目标值了,剪掉
path.pop_back();
return;
}
backtracking(k,n,i+1);
path.pop_back();
}
}
vector<vector<int>> combinationSum3(int k, int n) {
backtracking(k,n,1);
return ret;
}
};
class Solution {
public:
string path;
vector<string>ret;
const string letterMap[10] = {
"", // 0
"", // 1
"abc", // 2
"def", // 3
"ghi", // 4
"jkl", // 5
"mno", // 6
"pqrs", // 7
"tuv", // 8
"wxyz", // 9
};
void backtracking(const string& digits,int index){
if(index == digits.size()){// 每个数字对应的字母都取了一个,收集。
ret.push_back(path);;
return;
}
int digit = digits[index]-'0';//将对应char转为int
string letters = letterMap[digit];
for(int i = 0; i< letters.size();i++){
path.push_back(letters[i]);
backtracking(digits,index+1);
path.pop_back();
}
}
vector<string> letterCombinations(string digits) {
if(digits.size() == 0)return ret;
backtracking(digits,0);
return ret;
}
};
class Solution {
public:
const string letterMap[10] = {
"", // 0
"", // 1
"abc", // 2
"def", // 3
"ghi", // 4
"jkl", // 5
"mno", // 6
"pqrs", // 7
"tuv", // 8
"wxyz", // 9
};
vector<string>ret;
void backtracking(const string& digits,string s,int index){// 增加参数string
if(index == digits.size()){
ret.push_back(s);
return ;
}
int digit = digits[index]-'0';
string leeters = letterMap[digit];
for(int i =0; i < leeters.size();i++){
backtracking(digits,s+leeters[i],index+1);// 回溯
}
}
vector<string> letterCombinations(string digits) {
if(digits.size() == 0)return ret;
string s;
s.clear();
backtracking(digits,s,0);
return ret;
}
};
>- 方法1: 需要排序,因为按顺序取,需要判断是否超出目标值,超过则终止当前层的选取——剪枝。
class Solution {
public:
vector<vector<int>>ret;
vector<int>path;
void backtracking(vector<int>& candidates,int target,int startIndex){
if(accumulate(path.begin(),path.end(),0) == target){
ret.push_back(path);
return ;
}
for(int i = startIndex; i < candidates.size();i++){
path.push_back(candidates[i]);
if(accumulate(path.begin(),path.end(),0) > target){// 在这里终止,因为是排序过了,后面的只会更大。所以终止选取。
path.pop_back();
return;
}
backtracking(candidates,target,i);// 不加1,重复当前值
path.pop_back();
}
// 或者
// for(int i = startIndex; i < candidates.size() && accumulate(path.begin(),path.end(),0) < target;i++){
// path.push_back(candidates[i]);
// backtracking(candidates,target,i);
// path.pop_back();
// }
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
sort(candidates.begin(),candidates.end());// 注意排序
backtracking(candidates,target,0);
return ret;
}
};
注意对比与方法1终止条件位置的不同。
class Solution {
public:
vector<vector<int>>ret;
vector<int>path;
void backtracking(vector<int>& candidates,int target,int startIndex){
if(accumulate(path.begin(),path.end(),0) > target){// 超过目标值则回退,不要再选啦。
return ;
}
if(accumulate(path.begin(),path.end(),0) == target){
ret.push_back(path);
return ;
}
for(int i = startIndex; i < candidates.size();i++){
path.push_back(candidates[i]);
backtracking(candidates,target,i);
path.pop_back();
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
backtracking(candidates,target,0);
return ret;
}
};
class Solution {
public:
vector<int>path;
vector<vector<int>>ret;
void backtracking(vector<int>& candidates,int target,int stratIndex){
if(accumulate(path.begin(),path.end(),0) == target){
ret.push_back(path);
return ;
}
for(int i = stratIndex;i < candidates.size();i++){
// 去重
if(i > stratIndex && candidates[i] == candidates[i-1])continue;
path.push_back(candidates[i]);
if(accumulate(path.begin(),path.end(),0) > target){
path.pop_back();
return;
}
backtracking(candidates,target,i+1);
path.pop_back();
}
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
sort(candidates.begin(),candidates.end());
backtracking(candidates,target,0);
return ret;
}
};
下面的90题子集II有更详细的去重解释。
class Solution {
public:
vector<int>path;
vector<vector<int>>ret;
void backtracking(vector<int>& candidates,int target,int stratIndex,vector<bool>&used){
if(accumulate(path.begin(),path.end(),0) == target){
ret.push_back(path);
return ;
}
for(int i = stratIndex;i < candidates.size();i++){
// 去重,跳过同一层使用过的元素
// used[i-1] == true,表明同一个树枝candiates[i-1]使用过——联想往下纵向遍历。
// used[i-1] == false,表明同一个树层candiates[i-1]使用过——联想往右横向遍历。
// 为什么? used[i-1] == false;
// 因为,同一树层,used[i-1] == false才能表示,当前取的candidates[i]是从candidates[i-1]回溯而来。
// used[i-1] == true 说明进入下一层递归,向下纵向遍历,同一个树枝上使用过啦。
// 与前一个元素与当前元素相等,并且在同一个树枝使用过,去掉。
if(i > 0 && candidates[i] == candidates[i-1] &&
used[i-1] == false)continue;
path.push_back(candidates[i]);
if(accumulate(path.begin(),path.end(),0) > target){
path.pop_back();
return;
}
used[i] = true;// 树枝使用了,遍历
backtracking(candidates,target,i+1,used);
used[i] = false;
path.pop_back();
}
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
sort(candidates.begin(),candidates.end());
vector<bool>used(candidates.size(),false);
backtracking(candidates,target,0,used);
return ret;
}
};
class Solution {
public:
// 判断是否为回文子串-双指针
bool isPalindrome(const string& s,int start,int end){
for(int i = start,j = end; i < j;i++,j--){
if(s[i] != s[j])return false;
}
return true;
}
vector<vector<string>>ret;
vector<string>path;
void backtracking(const string& s, int startIndex){
if(startIndex >= s.size()){// 收集一轮结果
ret.push_back(path);
return ;
}
for(int i = startIndex;i < s.size();i++){
// 想象了一下搜索过程,每次递归刚进入先选一个字母,直到最后,然后开始返回,倒数第二次选两个,依次回溯调整选取状态...其中要判断每次选取的子串是否为回文子串。
if(isPalindrome(s,startIndex,i)){// 是回文字串
string str = s.substr(startIndex,i-startIndex+1);
path.push_back(str);// 收集
}else continue;//不是回文字串
backtracking(s,i+1);// 切割的地方不能重复切割,所以传入i+1
path.pop_back();// 回溯
}
}
vector<vector<string>> partition(string s) {
backtracking(s,0);
return ret;
}
};
class Solution {
public:
vector<vector<string>>ret;
vector<string>path;
vector<vector<bool>>isPalindrome;
void backtracking(const string& s, int startIndex){
if(startIndex >= s.size()){// 收集一轮结果
ret.push_back(path);
return ;
}
for(int i = startIndex;i < s.size();i++){
if(isPalindrome[startIndex][i]){// 是回文字串
string str = s.substr(startIndex,i-startIndex+1);
path.push_back(str);// 收集
}else continue;//不是回文字串
backtracking(s,i+1);
path.pop_back();// 回溯
}
}
// 计算每个子串是否为回文字串
void computePalindrome(const string& s){
isPalindrome.resize(s.size(),vector<bool>(s.size(),false));
for(int i = s.size()-1;i >= 0;i--){
for(int j = i;j < s.size();j++){
if(j ==i)isPalindrome[i][j] = true;
else if(j - i == 1)isPalindrome[i][j] = (s[i] == s[j]);
else isPalindrome[i][j] = (s[i]==s[j] && isPalindrome[i+1][j-1]);
}
}
}
vector<vector<string>> partition(string s) {
computePalindrome(s);
backtracking(s,0);
return ret;
}
};
class Solution {
public:
vector<string>ret;
bool isValid(const string& s,int start,int end){
if(start > end)return false;
if(s[start] == '0' && start != end)return false;// 前导0,不合法
int num = 0;
for(int i = start;i <= end;i++){
if(s[i] > '9' || s[i] < '0'){// 非数字
return false;
}
// 从前往后遍历一个数转成整型
num = num* 10 + (s[i]-'0');
if(num > 255)return false;
}
return true;
}
void backtracking(string& s,int startIndex,int pointCount){
if(pointCount == 3){
if(isValid(s,startIndex,s.size()-1)){// 判断最后一段是不是合法的
ret.push_back(s);// 在原字符串上修改
}
return ;
}
for(int i = startIndex;i < s.size();i++){
if(isValid(s,startIndex,i)){// 判断这个区间子串是否合法
s.insert(s.begin()+i+1,'.');// 在当前这个数字后面插入一个.
pointCount++;
backtracking(s,i+2,pointCount);// 注意,新加了一个点,所以要加2,到下一个数字
pointCount--;
s.erase(s.begin()+i+1);// 删除插入的点
}else break;//不合法直接结束
}
}
vector<string> restoreIpAddresses(string s) {
if(s.size() < 4 || s.size() > 12)return ret;// 剪枝
backtracking(s,0,0);
return ret;
}
};
class Solution {
public:
vector<int>used;// 记录是否使用过
vector<vector<int>>ret;
void backtracking(vector<int>& nums,int startIndex){
if(startIndex == nums.size()){// 到达边界出,开始收集结果
vector<int>path;
for(int i = 0; i < nums.size();i++){
if(used[i] == 1)path.push_back(nums[i]);
}
ret.push_back(path);
return;
}
// startIndex当前位置
used[startIndex] = 1;// 选当前位置
backtracking(nums,startIndex+1);
used[startIndex] = 0;// 恢复成未考虑状态
used[startIndex] = 2;// 不选当前位置
backtracking(nums,startIndex+1);
used[startIndex] = 0;// 恢复成未考虑状态
}
vector<vector<int>> subsets(vector<int>& nums) {
used.resize(nums.size(),0);
backtracking(nums,0);
return ret;
}
};
class Solution {
public:
vector<int>path;
vector<vector<int>>ret;
void backtracking(vector<int>& nums,int startIndex){
ret.push_back(path);// 收集结果
if(startIndex == nums.size())return ;// 此终止条件可以省略
for(int i = startIndex;i < nums.size(); i++){
path.push_back(nums[i]);// 选上当前元素
backtracking(nums,i+1);// 递归
path.pop_back();// 回溯-不选当前元素啦
}
}
vector<vector<int>> subsets(vector<int>& nums) {
backtracking(nums,0);
return ret;
}
};
class Solution {
public:
vector<int>path;
vector<vector<int>>ret;
vector<bool>used;
void backtracking(vector<int>&nums,int startIndex){
ret.push_back(path);// 注意本题收集结果的方式,每次递归都要收集结果。
if(startIndex == nums.size())return;// 这行可以不加
for(int i = startIndex;i < nums.size();i++){
// i > 0防止越界
if(i > 0 && nums[i] == nums[i-1] && used[i-1] == false)continue;// 去重
path.push_back(nums[i]);
used[i] = true;
backtracking(nums,i+1);
used[i] = false;
path.pop_back();
}
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
sort(nums.begin(),nums.end());// 排序,用于去重
used.resize(nums.size(),false);
backtracking(nums,0);
return ret;
}
};
class Solution {
public:
vector<int>path;
vector<vector<int>>ret;
void backtracking(vector<int>&nums,int startIndex){
ret.push_back(path);
for(int i = startIndex;i < nums.size();i++){
if(i > startIndex && nums[i] == nums[i-1])continue;// 去重
path.push_back(nums[i]);
backtracking(nums,i+1);
path.pop_back();
}
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
sort(nums.begin(),nums.end());// 排序,用于去重
backtracking(nums,0);
return ret;
}
};
if(i > 0 && nums[i] == nums[i-1] && used[i-1] == false)continue;// 去重
class Solution {
public:
vector<int>path;
vector<vector<int>>ret;
void backtracking(vector<int>&nums,int startIndex){
ret.push_back(path);
unordered_set<int>set;
for(int i = startIndex;i < nums.size();i++){
if(set.find(nums[i]) != set.end())continue;// 去重
path.push_back(nums[i]);
set.emplace(nums[i]);
backtracking(nums,i+1);
path.pop_back();
}
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
sort(nums.begin(),nums.end());// 排序,用于去重
backtracking(nums,0);
return ret;
}
};
class Solution {
public:
vector<int>path;
vector<vector<int>>ret;
void backtracking(vector<int>& nums,int startIndex){
// 题目要求,递增要求,至少有两个元素
if(path.size() > 1)ret.push_back(path);
unordered_set<int>set;
for(int i = startIndex;i < nums.size();i++){
if(!path.empty() && nums[i] < path.back() ||
set.find(nums[i]) != set.end())continue;
set.emplace(nums[i]);
path.push_back(nums[i]);
backtracking(nums,i+1);
// 为什么这里不用将set中的元素弹出来,因为我们每次递归进来都会创建一个set。是局部变量。详情见上图所示
// 即每个set只负责本层。
path.pop_back();
}
}
vector<vector<int>> findSubsequences(vector<int>& nums) {
backtracking(nums,0);
return ret;
}
};
class Solution {
public:
vector<int>path;
vector<vector<int>>ret;
void backtracking(vector<int>& nums,int startIndex){
// 题目要求,递增要求,至少有两个元素
if(path.size() > 1)ret.push_back(path);
int used[201] = {0};// 数据范围-100到+100
for(int i = startIndex;i < nums.size();i++){
if(!path.empty() && nums[i] < path.back() ||
used[nums[i]+100] == 1)continue;
used[nums[i]+100] = 1;
path.push_back(nums[i]);
backtracking(nums,i+1);
path.pop_back();
}
}
vector<vector<int>> findSubsequences(vector<int>& nums) {
backtracking(nums,0);
return ret;
}
};
class Solution {
public:
vector<int>path;
vector<vector<int>>ret;
vector<bool>used;// 记录当前元素是否使用过,true使用过,false没使用过
void backtracking(vector<int>& nums){
if(path.size() == nums.size()){
ret.push_back(path);
return ;
}
for(int i = 0; i < nums.size() ;i++){
if(used[i] == true)continue;// 该元素已经使用过啦
path.push_back(nums[i]);
used[i] = true;
backtracking(nums);
path.pop_back();
used[i] = false;
}
}
vector<vector<int>> permute(vector<int>& nums) {
used.resize(nums.size(),false);
backtracking(nums);
return ret;
}
};
先选中的这个数为下面树层的根。
class Solution {
public:
vector<int>path;
vector<vector<int>>ret;
vector<bool>used;// 用来记录是否使用过
void backtracking(vector<int>& nums){
if(path.size() == nums.size()){
ret.push_back(path);
return ;
}
for(int i = 0; i < nums.size();i++){
if(i > 0 && nums[i] == nums[i-1] && used[i-1] == false)continue;
if(used[i] == false){// 当前元素没使用,那就选上
used[i] = true;// 使用
path.push_back(nums[i]);
backtracking(nums);// 递归
used[i] = false;// 回溯
path.pop_back();
}
}
}
vector<vector<int>> permuteUnique(vector<int>& nums) {
used.resize(nums.size(),false);
sort(nums.begin(),nums.end());
backtracking(nums);
return ret;
}
};
即,对于树的每一个树枝来说,当我们确定了一个树层的根后,下面不可以再选与其相同的子根。
// 只需修改一步,将false改为true即可
// 如果理解了上面的我说的层的概念,这个树枝去重会很好理解。
if(i > 0 && nums[i] == nums[i-1] && used[i-1] == true)continue;
class Solution {
public:
vector<int>path;
vector<vector<int>>ret;
vector<bool>used;// 用来记录是否使用过
void backtracking(vector<int>& nums){
if(path.size() == nums.size()){
ret.push_back(path);
return ;
}
unordered_set<int>set;
for(int i = 0; i < nums.size();i++){
if(set.find(nums[i]) != set.end())continue;// 使用过了
if(used[i] == false){// 当前元素没使用,那就选上
used[i] = true;
set.insert(nums[i]);
path.push_back(nums[i]);
backtracking(nums);
used[i] = false;
path.pop_back();
}
}
}
vector<vector<int>> permuteUnique(vector<int>& nums) {
used.resize(nums.size(),false);
sort(nums.begin(),nums.end());
backtracking(nums);
return ret;
}
};
class Solution {
public:
// 某一个机场可以到达的机场都有哪些,航班次数为这样的航班有几个。
// 出发机场,<到达机场,航班次数>
unordered_map<string,map<string,int>>targets;
vector<string>ret;// 收集结果,沿途经过的机场数
bool backtracking(int ticketNum){
// ret.size()当前经过的机场数量 == 航班数量+1
// 就是将所有票都用上了,终止。
if(ret.size() == ticketNum + 1)return true;// 当前票都使用完毕
//每次遍历的时候拿到上一个降落的机场,遍历这个机场接下来可以往哪飞
for(pair<const string,int>& target : targets[ret[ret.size()-1]]){// 注意传引用,修改second值,const-因为map中的key不可修改。
if(target.second > 0){//
ret.push_back(target.first);
target.second--;
if(backtracking(ticketNum))return true;// 找到了终止,就不pop出来了
ret.pop_back();// 回溯
target.second++;
}
}
return false;
}
vector<string> findItinerary(vector<vector<string>>& tickets) {
targets.clear();
for(const vector<string>& vec: tickets){// 记录映射关系-当前机场都能到哪个机场,能到达几次
targets[vec[0]][vec[1]]++;// 计数,使用了当前票--,避免死循环。
}
ret.push_back("JFK");// 起始机场
backtracking(tickets.size());
return ret;
}
};
,和子集问题同理
class Solution {
public:
vector<vector<string>>ret;
vector<string>chess_borad;
void backtracking(int n,int row){// n为棋盘大小,row为当前到第几行
if(row == n){// 每行都确定了皇后的位置
ret.push_back(chess_borad);
return ;
}
for(int col = 0; col < n;col++){
if(isValid(row,col,n)){// 当前位置合法
chess_borad[row][col] = 'Q';// 放置皇后
backtracking(n,row+1);
chess_borad[row][col] = '.';// 撤销皇后
}
}
}
// 判断当前位置放入皇后,是否合法
bool isValid(int row,int col,int n){
// 检查列
for(int i = 0; i < row;i++){
if(chess_borad[i][col] == 'Q')return false;
}
// 为什么这里没有单独检查行呢,因为递归的时候就是在调整行,递归一下,切换一下行。
// 45度——也就是往右上判断,为什么不用判断左下判断,因为还没到下面的行。
// 135度判断同理
for(int i = row - 1,j = col + 1 ;i >=0 && j < n;i--,j++){
if(chess_borad[i][j] == 'Q')return false;
}
for(int i = row - 1,j = col - 1; i >= 0 && j >= 0;i--,j--){
if(chess_borad[i][j] == 'Q')return false;
}
return true;
}
vector<vector<string>> solveNQueens(int n) {
chess_borad.resize(n,string(n,'.'));
backtracking(n,0);
return ret;
}
};
,m是’.'的数目
,递归的深度是
class Solution {
public:
bool isValid(int row,int col,char val,vector<vector<char>> &board){
for(int i = 0; i < 9;i++){// 检查同行是否有重复
if(board[row][i] == val)return false;
}
for(int i = 0;i < 9;i++){// 检查同列是否有重复
if(board[i][col] == val)return false;
}
// 检查九宫格内是否有重复
int startRow = (row / 3)*3;//定位每个九宫格左上角位置
int startCol = (col / 3)*3;
for(int i = startRow;i < startRow + 3;i++){
for(int j = startCol;j < startCol + 3;j++){
if(board[i][j] == val)return false;
}
}
return true;
}
bool backtracking(vector<vector<char>>& board){
// 有趣的一点,我们发现在这里并没有控制下标访问,因为我们将某个空白位置放上了数字,board[i][j]就不为'.'啦,递归时就不会覆盖上一层所放的位置
// 递归的时候会不断找到空的位置,尝试放入合法的数。也就是说每次层递归,都确定一个空位置被放入一个合法的数。
// 就是一个萝卜一个坑,理论上来说有几个空白位置就会递归几层。递归只是为了找到坑位,不会再向后移动,搜寻别的坑。
// 当某一层递归中,某一个空白放哪个数字都不行,return false,回溯,尝试将上一层所放置的位置换成别的,继续调整。
for(int i = 0; i < 9;i++){
for(int j = 0; j < 9;j++){
if(board[i][j] == '.'){// 是空白
for(char k = '1' ;k <= '9';k++){// 遍历,放置合适的数字
if(isValid(i,j,k,board)){// 可以放
board[i][j] = k;
if(backtracking(board))return true;// 可以了就终止
board[i][j] = '.';
}
}
return false;// 九个数都是了,放哪个都不行,return false
}
}
}
return true;
}
void solveSudoku(vector<vector<char>>& board) {
backtracking(board);
}
};