回溯常用于解决
回溯其实就是暴力搜索,回溯是递归的副产品,只要有递归就有回溯
回溯三部曲:
void backtracking(参数)
if(终止条件)
{
存放结果;
return;
}
for(选择:本层集合中的元素)
{
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果;
}
整体框架如下:
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
class Solution {
private:
vector> result;
vector path;
void backtracking(int n, int k, int startindex)
{
if(path.size()==k)
{
result.push_back(path);
return;
}
// 当可供选择的不足时,剪枝
for(int i=startindex;i<=n-(k-path.size())+1;i++) // 这里涉及到剪枝优化
{
path.push_back(i);
backtracking(n,k,i+1);
path.pop_back(); // 回退
}
return;
}
public:
vector> combine(int n, int k) {
result.clear();
path.clear();
backtracking(n,k,1);
return result;
}
};
class Solution {
private:
vector> result;
vector path;
void backtracking(int k, int target, int sum, int startIndex) // 确定要传递的参数和返回值
{
if(sum>target) // 这里设计一部分剪枝
return;
// 确定终止条件并保存结果
if(path.size()==k)
{
if(sum==target)
result.push_back(path);
return;
}
// 回溯搜索过程
for(int i = startIndex; i<=9-(k-path.size())+1;i++) // 这里涉及一部分剪枝
{
sum+=i;
path.push_back(i);
backtracking(k,target,sum,i+1);
sum-=i; // 别忘了减
path.pop_back();
}
return;
}
public:
vector> combinationSum3(int k, int n) {
backtracking(k,n,0,1);
return result;
}
};
class Solution {
private:
const string lettermap[8] ={
"abc",
"def",
"ghi",
"jkl",
"mno",
"pqrs",
"tuv",
"wxyz"
};
vector result;
string s;
void backtracking(const string& digits, int index)
{
if(index==digits.size())
{
result.push_back(s);
return;
}
// 确定单层递归逻辑
int dig = digits[index] - '0'; // 转化成数字
string letters = lettermap[dig-2]; // 取字符串
for(int i=0;i letterCombinations(string digits) {
if(digits.size()==0) // 对空进行处理
return result;
backtracking(digits,0);
return result;
}
};
class Solution {
private:
vector> result;
vector path;
void backtracking(vector& candidates, int target, int sum, int index)
{
if(sum>target)
return;
// 不再是数字
if(sum==target)
{
result.push_back(path);
return;
}
// 回溯搜索过程
for(int i = index;i> combinationSum(vector& candidates, int target) {
// 注意这题需要先排序,否则没办法进行剪枝
// sort(candidates.begin(),candidates.end());
backtracking(candidates,target,0,0);
return result;
}
};
// 这题关键是里面存在重复的元素,所以需要添加一个map来记录,用数组即可
// 每个数字在每个组合中只能使用一次,在同一层上进行去重
// 所以可以先对数组排序,然后就可以方便对相同的元素进行去重
class Solution {
private:
vector> result;
vector path;
void backtracking(vector& candidates, int target, int sum, int index, vector used)
{
if(sum>target)
return;
if(sum==target)
{
result.push_back(path);
return;
}
// 单层递归逻辑
for(int i=index; i0 && candidates[i]==candidates[i-1] && used[i-1]==false)
continue;
used[i] = true;
sum+=candidates[i];
path.push_back(candidates[i]);
backtracking(candidates,target,sum,i+1,used); // 注意因为不能重复
sum-=candidates[i];
path.pop_back();
used[i] = false;
}
}
public:
vector> combinationSum2(vector& candidates, int target) {
vector used(candidates.size(),false);
sort(candidates.begin(),candidates.end());
backtracking(candidates,target,0,0,used);
return result;
}
};
class Solution {
private:
vector> result;
vector path;
bool isPalindrome(const string& s, int start, int end)
{
for(int i=start, j=end; i=s.size())
{
result.push_back(path);
return;
}
// 单层搜索逻辑
for(int i=index; i> partition(string s) {
backtracking(s,0);
return result;
}
};
// 时间复杂度O(3^4)
// 空间复杂度O(n)
class Solution {
private:
vector result;
bool isValid(const string& s, int start, int end)
{
if(start>end)
return false;
if(s[start]=='0' && start!=end) // 开头为0并且不止一位数
return false;
int num=0;
for(int i=start;i<=end;i++) // 不能大于255
{
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 index, int num)
{
if(num==3) // 已经插入3个.
{
if(isValid(s,index,s.size()-1)) // 需要检查第四段是否合法
result.push_back(s);
return;
}
// 单层搜索逻辑
for(int i=index;i restoreIpAddresses(string s) {
if(s.size()<13) // 容易忽视
backtracking(s,0,0);
return result;
}
};
组合问题和分割问题都是收集树的叶子节点,而子集问题是找到树的所有节点。
class Solution {
private:
vector> result;
vector path;
void backtracking(vector& nums, int index)
{
// 因为每个节点都存,这里不需要判断index> subsets(vector& nums) {
backtracking(nums,0);
return result;
}
};
// 提高难度,需要排除重复的,所以引入一个map,但是需要先对nums排序,和前面的排列组合问题一个思路
// 排序O(nlogn),子集最多2^n个,每个子集加入答案时需要拷贝一份,耗时O(n),最后时间复杂度为O(n*2^n);
// 空间复杂度O(n)
class Solution {
private:
vector> result;
vector path;
void backtracking(vector& nums, int index, vector& used)
{
result.push_back(path);
// 单层搜索逻辑
for(int i=index; i0 && nums[i]==nums[i-1] && used[i-1]==false)
continue;
used[i] = true;
path.push_back(nums[i]);
backtracking(nums,i+1,used);
used[i]=false;
path.pop_back();
}
return;
}
public:
vector> subsetsWithDup(vector& nums) {
sort(nums.begin(),nums.end());
vector used(nums.size(),false);
backtracking(nums,0,used);
return result;
}
};
排列问题也是递归到叶子节点
// 时间复杂度O(n*n!)
// 空间复杂度O(n)
class Solution {
private:
vector> result;
vector path;
void backtracking(vector& nums, vector& used)
{
if(path.size()==nums.size())
{
result.push_back(path);
return;
}
// 单层搜索逻辑
for(int i=0;i> permute(vector& nums) {
vector used(nums.size(),false);
backtracking(nums,used);
return result;
}
};
// 时间复杂度O(n*n!)
// 空间复杂度O(n)
class Solution {
private:
vector> result;
vector path;
void backtracking(vector& nums, vector& used)
{
if(path.size()==nums.size())
{
result.push_back(path);
return;
}
// 单层搜索逻辑
for(int i=0; i0 && nums[i]==nums[i-1] && used[i-1]==false) || used[i]==true)
continue;
used[i] = true;
path.push_back(nums[i]);
backtracking(nums,used);
used[i] = false;
path.pop_back();
}
}
public:
vector> permuteUnique(vector& nums) {
sort(nums.begin(),nums.end());
vector used(nums.size(),false);
backtracking(nums,used);
return result;
}
};
class Solution {
private:
vector> result;
bool isvalid(int row, int col, vector& chessboard,int n)
{
// 检查列
for(int i=0;i=0&&i>=0;j--,i--)
{
if(chessboard[i][j]=='Q')
return false;
}
// 检查135°
for(int i=row-1,j=col+1;i>=0&&j& chessboard)
{
if(row==n)
{
result.push_back(chessboard);
return;
}
// 单层的搜索逻辑
for(int col=0; col> solveNQueens(int n) {
vector chessboard(n,string(n,'.'));
backtracking(0,n,chessboard);
return result;
}
};
class Solution {
private:
bool isValid(int row, int col, char val, vector>& board)
{
// 判断行内是否重复
for(int i=0; i<9; i++)
{
if(board[row][i]==val)
return false;
}
// 判断列内是否重复
for(int j=0; j<9; j++)
{
if(board[j][col]==val)
return false;
}
// 判断3*3宫格内是否重复
int startRow = (row/3)*3;
int startCol = (col/3)*3;
for(int i=startRow; i>& board)
{
// 遍历整个树形结构找到可能的叶子节点就返回,不需要终止条件
// 递归单层逻辑,需要一个二维的递归
for(int i=0; i>& board) {
backtracking(board);
}
};
class Solution {
private:
vector> result;
vector path;
void backtracking(vector& nums, int index)
{
// 第二层开始的所有节点
if(path.size()>1) // 将合法的结果存
result.push_back(path);
// unordered_set used;
// 用数组更好
vector used(201,false);
// 单层搜索逻辑,也要考虑重复问题
for(int i=index; i> findSubsequences(vector& nums) {
// vector used(nums.size(),false); // 惯性思维,nums是乱序的呀
backtracking(nums,0);
return result;
}
};
// 这个题可以用回溯来解决
// 需要借助一个映射来记录起点和终点,并且要记录从起点到终点的剩余票数
// unordered_map> targets; //unordered_map<出发机场,map<到达机场,航班数>>
class Solution {
private:
// 要找到一个对数据进行排序的容器,而且还要容易增删元素,迭代器还不能失效。
// 选择了unordered_map> targets 来做机场之间的映射。
unordered_map> targets;
bool backtracking(int ticketNum, vector& result)
{
if(result.size() == ticketNum+1) // 结束行程时,经过的机场个数==航班数+1
return true;
// 单层遍历逻辑
for(pair& target : targets[result[result.size()-1]]) // 遍历所有以result最后一个机场为起点的所有航班,这里用到迭代器
{
// 判断一下这个航班是否飞过
if(target.second > 0)
{
result.push_back(target.first);
target.second--;
if(backtracking(ticketNum,result)) return true;
result.pop_back();
target.second++;
}
}
return false;
}
public:
vector findItinerary(vector>& tickets) {
vector result;
// 建立映射关系
for(const vector& vec:tickets)
targets[vec[0]][vec[1]]++;
result.push_back("JFK"); // 别忘了定义初始
backtracking(tickets.size(),result);
return result;
}
};