这是一个目录
什么是回溯法
回溯和递归相辅相成,递归函数的下面是回溯的过程。回溯法:纯暴力的搜索,抽象成树形结构,回溯法抽象为树形结构后,遍历过程就是:for循环横向遍历,递归纵向遍历,回溯不断调整结果集。
回溯算法模板框架:
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
回溯算法能解决的问题
组合问题:N个数里面按一定规则找出k个数的集合
排列问题:N个数按一定规则全排列,有几种排列方式
切割问题:一个字符串按一定规则有几种切割方式
子集问题:一个N个数的集合里有多少符合条件的子集
棋盘问题:N皇后,解数独
组合问题
给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。
示例: 输入: n = 4, k = 2 输出: [ [2,4], [3,4], [2,3], [1,2], [1,3], [1,4], ]
解决k层for循环嵌套的问题,回溯法三部曲
class Solution {
public:
vector<vector<int>> result;//存放符合条件的结果集合
vector<int> path;//符合条件的结果
void backtracking(int n, int k, int index){
if(path.size() == k){//终止条件
result.push_back(path);
return;
}
for(int i = index; i <= n; i++){//控制横向遍历
path.push_back(i);//处理节点
backtracking(n, k, i+1);//递归,下一层搜索从i+1开始
path.pop_back();//回溯,撤销处理结果
}
}
vector<vector<int>> combine(int n, int k) {
backtracking(n, k, 1);
return result;
}
};
组合优化
剪枝优化
class Solution {
public:
vector<vector<int>> result;//存放符合条件的结果集合
vector<int> path;//符合条件的结果
void backtracking(int n, int k, int index){
if(path.size() == k){//终止条件
result.push_back(path);
return;
}
for(int i = index; i <= n - (k - path.size()) + 1; i++){//控制横向遍历,优化
path.push_back(i);//处理节点
backtracking(n, k, i+1);//递归,下一层搜索从i+1开始
path.pop_back();//回溯,撤销处理结果
}
}
vector<vector<int>> combine(int n, int k) {
backtracking(n, k, 1);
return result;
}
};
组合总和|||
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
void backtracking(int targetSum, int k, int pathsum, int index){
if(path.size()==k){
if(pathsum == targetSum) result.push_back(path);
return;
}
for(int i = index; i <= 9; i++){
pathsum += i;//处理
path.push_back(i);//处理
backtracking(targetSum, k, pathsum, i + 1);
pathsum -= i;//回溯
path.pop_back();//回溯
}
}
vector<vector<int>> combinationSum3(int k, int n) {
backtracking(n, k, 0, 1);
return result;
}
};
电话号码的字母组合
string letterMap[10]={
"",//0
"",//1
"abc",//2
"def",//3
"ghi",//4
"jkl",//5
"mno",//6
"pqrs",//7
"tuv",//8
"wxyz",//9
};
string s;
vector<string> result;
//index是输入数字组合的下标
void backtracking(string digits, int index){
if(index==digits.size()){
return.push_back(s);
return;
}
int digit = digits[index]-'0';
string letter = letterMap[digit]
for(int i=0;i<letter.size();i++){
s.push_back(letter[i]);
backtracking(digits,index+1);
s.pop_back();
}
}
完整代码:
class Solution {
public:
string letterMap[10] = {
"",//0
"",//1
"abc",//2
"def",//3
"ghi",//4
"jkl",//5
"mno",//6
"pqrs",//7
"tuv",//8
"wxyz",//9
};
vector<string> result;
string s;
void backtracking(string digits, int index){
if(index==digits.size()){
result.push_back(s);
return;
}
int digit = digits[index] - '0';//index指向的数字转成int类型
string letter = letterMap[digit];
for(int i=0; i<letter.size(); i++){
s.push_back(letter[i]);//处理
backtracking(digits, index+1);//递归,处理下一个数字index+1
s.pop_back()//回溯
}
}
vector<string> letterCombinations(string digits) {
if(digits.size()==0) return result;
backtracking(digits, 0);
return result;
}
};
组合总和
**题目:**给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。candidates 中的数字可以无限制重复被选取。
所有数字(包括 target)都是正整数,解集不能包含重复的组合。
示例:
输入:candidates = [2,3,6,7], target = 7,
所求解集为: [ [7], [2,2,3] ]
组合没有数量要求;元素可以重复提取
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& candidates, int target, int sum, int startindex){
//终止条件
if(sum>target) return;
if(sum==target){
result.push_back(path);
return;
}
//单层搜索逻辑
for(int i=startindex; i<candidates.size(); i++){
sum+=candidates[i];
path.push_back(candidates[i]);
backtracking(candidates, target, sum, i);//i,元素可重复使用
sum-=candidates[i];//回溯
path.pop_back();//回溯
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
backtracking(candidates, target, 0, 0);
return result;
}
};
组合总和||
题目:给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的每个数字在每个组合中只能使用 一次 。
注意:解集不能包含重复的组合。
代码:
//排序,去重
//树层去重,树枝去重
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int>& nums, int sum, int startindex, int target, vector<bool> used){
if(sum > target) return;
if(sum == target){
result.push_back(path);
return;
}
for(int i = startindex; i < nums.size(); i++){
if(i>0 && nums[i] == nums[i-1] && used[i-1] == 0) continue;
path.push_back(nums[i]);
sum += nums[i];
used[i] = true;
backtracking(nums, sum, i+1, target, used);
sum -= nums[i];//回溯
used[i] = false;
path.pop_back();
}
}
vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
int n = candidates.size();
vector<bool> used(n, false);
sort(candidates.begin(), candidates.end());
backtracking(candidates, 0, 0, target, used);
return result;
}
};
分割回文串
题目:给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
返回 s 所有可能的分割方案。
示例: 输入: “aab” 输出: [ [“aa”,“b”], [“a”,“a”,“b”] ]
class Solution {
public:
vector<vector<string>> result;//所有结果集
vector<string> path;//回文的子串
//判断是否是回文串,双指针
bool isHuiwen(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;
}
void backtracking(string s, int startIndex){
//起始位置大于s的大小,找到一组分割方案
if(startIndex >= s.size()){
result.push_back(path);
return;
}
for(int i = startIndex; i < s.size(); i++){
if(isHuiwen(s,startIndex,i)){
//是回文,获取[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 result;
}
};
复原IP地址
**题目:**给定一个只包含数字的字符串,复原它并返回所有可能的 IP 地址格式。
有效的 IP 地址 正好由四个整数(每个整数位于 0 到 255 之间组成,且不能含有前导 0),整数之间用 ‘.’ 分隔。
例如:“0.1.2.201” 和 “192.168.1.1” 是 有效的 IP 地址,但是 “0.011.255.245”、“192.168.1.312” 和 “192.168@1.1” 是 无效的 IP 地址。
示例 1:
输入:s = “25525511135”
输出:[“255.255.11.135”,“255.255.111.35”]
0 <= s.length <= 3000
s 仅由数字组成
思路: 分割、合法性判断
vector<string> result;
void backtracking(s, startIndex, pointSum){
if(pointSum == 3){//逗点
if(isValid(s, startIndex, s.size()-1)){
result.push_back(s);
return;
}
}
for(i = startIndex; i < s.size(); i++){
if(isValid(s,startIndex,i)){//[stratIndex,i]
s.insert(s.begin() + i + 1, '.');//插入逗点
pointSum += 1;
backtracking(s, i+2, pointSum);
s.erase(s.begin() + i + 1);//删掉逗点
pointSum -= 1;//回溯
}
}
}
class Solution {
public:
vector<string> result;//存储合法的IP字符串
//判断是否合法
bool isValid(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;//数字大于255
}
return true;
}
void backtracking(string s, int starIndex, int pointNum){
if(pointNum == 3){
if(isValid(s, starIndex, s.size()-1)){
result.push_back(s);
}
return;
}
for(int i = starIndex; i < s.size(); i++){
if(isValid(s, starIndex, i)){
s.insert(s.begin() + i + 1, '.');//插入逗点
pointNum++;//逗点数量加1
backtracking(s, i + 2, pointNum);
pointNum--;//逗点数量减1 回溯
s.erase(s.begin() + i + 1);//删掉逗点
}else break;
}
}
vector<string> restoreIpAddresses(string s) {
if(s.size() > 12 || s.size() < 4) return result;
backtracking(s, 0, 0);
return result;
}
};
子集
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
说明:解集不能包含重复的子集。
示例: 输入: nums = [1,2,3] 输出: [ [3], [1], [2], [1,2,3], [1,3], [2,3], [1,2], [] ]
path
result
void backtracking(nums, startIndex){
result.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();
}
return;
}
完整代码:
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int> nums, int startIndex){
result.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();//回溯
}
return;
}
vector<vector<int>> subsets(vector<int>& nums) {
backtracking(nums, 0);
return result;
}
};
子集 II
给你一个整数数组 nums,其中可能包含重复元素,请你返回该数组所有可能的
子集(幂集)。
解集不能包含重复的子集。返回的解集中,子集可以按 任意顺序排列。
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int> nums, int startIndex, vector<bool> used){
result.push_back(path);
for(int i = startIndex; i < nums.size(); i++){
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);
used[i] = false;
path.pop_back();
}
return;
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
vector<bool> used(nums.size(), false);
sort(nums.begin(), nums.end());//去重排序
backtracking(nums, 0, used);
return result;
}
};
491.递增子序列
给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2。
示例:
输入: [4, 6, 7, 7]
输出: [[4, 6], [4, 7], [4, 6, 7], [4, 6, 7, 7], [6, 7], [6, 7, 7], [7,7], [4,7,7]]
说明:
给定数组的长度不会超过15。
数组中的整数范围是 [-100,100]。
给定数组中可能包含重复数字,相等的数字应该被视为递增的一种情况。
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int> nums, int startIndex){
unordered_set<int> uset;//对本层元素去重
if(path.size()>1) result.push_back(path);
for(int i=startIndex; i<nums.size(); i++){
if((!path.empty() && nums[i]<path.back())||uset.find(nums[i])!=uset.end()) continue;
uset.insert(nums[i]);
path.push_back(nums[i]);
backtracking(nums, i+1);
path.pop_back();
}
}
vector<vector<int>> findSubsequences(vector<int>& nums) {
backtracking(nums, 0);
return result;
}
};
46. 全排列
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int> nums, vector<bool> used){
if(path.size() == nums.size()){
result.push_back(path);
return;
}
for(int i=0; i<nums.size(); i++){
if(used[i]==true) continue;
used[i]=true;
path.push_back(nums[i]);
backtracking(nums, used);
used[i]=false;
path.pop_back();
}
}
vector<vector<int>> permute(vector<int>& nums) {
vector<bool> used(nums.size(), false);
backtracking(nums, used);
return result;
}
};
47. 全排列 II
给定一个可包含重复数字的序列nums,按任意顺序返回所有不重复的全排列。
class Solution {
public:
vector<vector<int>> result;
vector<int> path;
void backtracking(vector<int> nums, vector<bool> used){
if(path.size() == nums.size()){
result.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]==true) continue;
used[i] = true;
path.push_back(nums[i]);
backtracking(nums, used);
path.pop_back();
used[i] = false;
}
}
vector<vector<int>> permuteUnique(vector<int>& nums) {
vector<bool> used(nums.size(), false);
sort(nums.begin(), nums.end());
backtracking(nums, used);
return result;
}
};
重新安排行程
给定一个机票的字符串二维数组 [from, to],子数组中的两个成员分别表示飞机出发和降落的机场地点,对该行程进行重新规划排序。所有这些机票都属于一个从 JFK(肯尼迪国际机场)出发的先生,所以该行程必须从 JFK 开始。
示例 1:
输入:[[“MUC”, “LHR”], [“JFK”, “MUC”], [“SFO”, “SJC”], [“LHR”, “SFO”]]
输出:[“JFK”, “MUC”, “LHR”, “SFO”, “SJC”]
这道题很难,看的题解敲的
class Solution {
public:
unordered_map<string, map<string,int>> targets;
bool backtracking(int ticketnum, vector<string>& result){
if(result.size()==ticketnum+1) return true;
for(pair<const string, int>& target : targets[result[result.size()-1]]){
if(target.second>0){
result.push_back(target.first);
target.second--;
if(backtracking(ticketnum,result)) return true;
result.pop_back();
target.second++;
}
}
return false;
}
vector<string> findItinerary(vector<vector<string>>& tickets) {
vector<string> result;
for(const vector<string> vec : tickets){
targets[vec[0]][vec[1]]++;
}
result.push_back("JFK");
backtracking(tickets.size(), result);
return result;
}
};
N皇后
n 皇后问题 研究的是如何将 n 个皇后放置在 n×n 的棋盘上,并且使皇后彼此之间不能相互攻击。
给你一个整数 n ,返回所有不同的 n 皇后问题 的解决方案。
每一种解法包含一个不同的 n 皇后问题 的棋子放置方案,该方案中 ‘Q’ 和 ‘.’ 分别代表了皇后和空位。
输入:n = 4
输出:[[“.Q…”,“…Q”,“Q…”,“…Q.”],[“…Q.”,“Q…”,“…Q”,“.Q…”]]
解释:如上图所示,4 皇后问题存在两个不同的解法。
思路:
约束条件:不能同行、不能同列、不能同斜线
棋盘的宽度就是for循环的长度,递归的深度就是棋盘的高度
class Solution {
public:
vector<vector<string>> result;
void backtracking(int row, int n, vector<string>& chessboard){
if(row==n){
result.push_back(chessboard);
return;
}
for(int col=0; col<n; col++){
if(isValid(row, col, chessboard, n)){
chessboard[row][col]='Q';
backtracking(row+1,n,chessboard);
chessboard[row][col]='.';
}
}
}
bool isValid(int row, int col, vector<string>& chessboard,int n){
for(int i=0;i<row;i++){
if(chessboard[i][col]=='Q'){
return false;
}
}
for(int i=row-1, j=col-1; i>=0&&j>=0; i--, j--){
if(chessboard[i][j]=='Q') return false;
}
for(int i=row-1, j=col+1; i>=0&&j<n; i--, j++){
if(chessboard[i][j]=='Q') return false;
}
return true;
}
vector<vector<string>> solveNQueens(int n) {
std::vector<std::string> chessboard(n,std::string(n,'.');
backtracking(0, n, chessboard);
return result;
}
};
数独
二维递归
class Solution {
public:
bool backtracking(vector<vector<char>>& board){
for(int i = 0; i < board.size(); i++){//遍历行
for(int j = 0; j < board[0].size(); 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;//9个数试完了,还是不行,返回false
}
}
}
return true;
}
bool isValid(int row, int col, char k, vector<vector<char>>& board){
for(int i=0; i<9; i++){//是否行重复
if(board[row][i]==k) return false;
}
for(int j=0; j<9; j++){//是否列重复
if(board[j][col]==k) 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]==k) return false;
}
}
return true;
}
void solveSudoku(vector<vector<char>>& board) {
backtracking(board);
}
};