回溯法
基本套路
套路来源于代码随想录模板
void backtracking(参数) {
if (终止条件) {
存放结果;
return;
}
for (选择:本层集合中元素(树中节点孩子的数量就是集合的大小)) {
处理节点;
backtracking(路径,选择列表); // 递归
回溯,撤销处理结果
}
}
排列问题
leetcode46 全排列
给定一个不含重复数字的数组 nums ,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
//递归深度 nums.sze(), 每层宽度num.size()剪枝已经使用过的。
相较模板特殊之处:used数组
class Solution {
vector<vector<int>> res;
vector<int> path;
public:
vector<vector<int>> permute(vector<int>& nums) {
res.clear();
path.clear();
vector<bool> used(nums.size(), false);
backtracking(nums, used);
return res;
}
void backtracking(vector<int>& nums,vector<bool>&used) {//参数是已经走过的路径和总集合
if (path.size()==nums.size()) {
res.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,used);//选择列表(路径被设为全局变量了)
path.pop_back();//回溯
used[i]=false;
}
}
};
class Solution {
List<List<Integer>>ans=new ArrayList<>();
LinkedList<Integer> path=new LinkedList<>();
public List<List<Integer>> permute(int[] nums) {
boolean []used=new boolean[nums.length];//和c++区别
if(nums.length==0)
return ans;
backtracking(nums, used);
return ans;
}
private void backtracking(int[] nums,boolean[] used)
{
if(path.size()==nums.length)
{
ans.add(new ArrayList<>(path));//重点关注:这里的add方法表明将ans的尾部指向***所以不能写ans.add(path)而是要重新开辟一片空间来写,否则path变化时ans中的值也会跟着变化
}
for(int i=0;i<nums.length;i++)
{
if(used[i])
{continue;}
path.add(nums[i]);
used[i]=true;
backtracking(nums, used);
used[i]=false;
path.removeLast();
}
}
}
剑指38 全排列(含有重复字符)
剑指 Offer 38. 字符串的排列 输入一个字符串,打印出该字符串中字符的所有排列。
你可以以任意顺序返回这个字符串数组,但里面不能有重复元素。
示例:
输入:s = “abc” 输出:[“abc”,“acb”,“bac”,“bca”,“cab”,“cba”]
string----------vector char 转换
(方法来源https://blog.csdn.net/wangshubo1989/article/details/50274289/)
---------vector<char>如何转string:
std::vector<char> data ;
std::string res;
//方法一
for (int i = 0;i<data.size();++i) {
res+=data[i];
}
res+='\0';
std:cout << res;
//方法二
std::vector<char> data ;
std::string res;
res.insert(res.begin(), data.begin(), data.end());
std::cout << res;
//方法三
std::vector<char> *data = response->getResponseData();
std::string res;
const char* s = &(*data->begin());
res = std::string(s, data->size());
std::cout << res;
-----string转化为vector<char>
string ch = "what a fucking day!";
vector <char> ta;
ta.resize(ch.size());
ta.assign(ch.begin(),ch.end());
程序代码:
class Solution {
public:
vector<char> path;
vector<string> res;
vector<string> permutation(string s) {
path.clear();
res.clear();
sort(s.begin(),s.end());//排序对去重很关键
vector<bool>used(s.length(),false);
backtracking(s, used);
return res;
}
void backtracking(string &s,vector<bool>&used)
{
if(s.length()==path.size())
{
string m;
for(int i=0;i<path.size();i++)
{
m+=path[i];
}
res.push_back(m);
return;
}
for(int i=0;i<s.length();i++)
{
//关于去重同一层:used[i-1]==false代表在同层已用过
//used[i-1]==true代表在同支曾用过
if(used[i]==true)
continue;
if(i>0&&s[i]==s[i-1]&&used[i-1]==false)//去重
{
continue;
}
used[i]=true;
path.push_back(s[i]);
backtracking(s,used);
used[i]=false;//回溯
path.pop_back();
}
}
};
leetcode47全排列(含有重复数字)
- 全排列 II
给定一个可包含重复数字的序列 nums ,按任意顺序 返回所有不重复的全排列。
示例 1:
输入:nums = [1,1,2]
输出:
[[1,1,2],
[1,2,1],
[2,1,1]]
class Solution {
public:
vector<vector<int>> res;
vector<int> path;
vector<vector<int>> permuteUnique(vector<int>& nums) {
res.clear();
path.clear();
sort(nums.begin(),nums.end());
vector<bool> used(nums.size(),false);
backtracking(nums,used);
return res;
}
void backtracking(vector<int>&nums,vector<bool>&used)
{
if(nums.size()==path.size())
{
res.push_back(path);
return;
}
for(int i=0;i<nums.size();i++)
{
if(used[i]==true)
continue;
if(i>0&&nums[i]==nums[i-1]&&used[i-1]==false)
continue;
used[i]=true;
path.push_back(nums[i]);
backtracking(nums,used);
used[i]=false;
path.pop_back();
}
}
};
组合问题
leetcode 77 组合问题
- 组合
给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。
示例:
输入: n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
class Solution {
public:
vector<vector<int>> res;
vector<int> path;
void backtracking(int n,int k,int startindex)//待搜索路径,从左向右拿数字,不存在重复数字
{
if(path.size()==k)
{
res.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 res;
}
};
leetcode39 组合总和(有重复,无限深度)
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。
java版本
class Solution {
List<List<Integer>> res =new ArrayList<>();
LinkedList<Integer > path=new LinkedList<>();
public List<List<Integer>> combinationSum(int[] candidates, int target) {
if(candidates.length==0)
return res;
backtracing(candidates, target, 0, 0);
return res;
}
private void backtracing (int []candidates,int target, int startindex,int sum)
{
if(sum>target)
{
return;
}
if(sum==target)
{
res.add(new ArrayList<>(path));
return;
}
for(int i=startindex;i<candidates.length;i++)
{
sum=sum+candidates[i];
path.add(candidates[i]);
backtracing(candidates, target, i, sum);
sum=sum-candidates[i];
path.removeLast();
}
}
}
子集问题
leetcode90子集(有重复)
- 子集 II
给你一个整数数组 nums ,其中可能包含重复元素,请你返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。返回的解集中,子集可以按 任意顺序 排列。
示例 1:输入:nums = [1,2,2]
输出:[[],[1],[1,2],[1,2,2],[2],[2,2]]
示例 2:输入:nums = [0]
输出:[[],[0]]
class Solution {
public:
vector<vector<int>> res;
vector<int> path;
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
vector<bool>used(nums.size(),false);
sort(nums.begin(),nums.end());
for(int i=0;i<=nums.size();i++)
backtracking(used, 0, nums, i);
return res;
}
void backtracking(vector<bool>used,int startindex,vector<int> nums,int k)
{
if(path.size()==k)
{
res.push_back(path);
return;//可以不加return因为子集问题遍历整棵树,不需要剪枝
for(int i=startindex;i<nums.size();i++)
{
if(i>0&&used[i-1]==false&&nums[i-1]==nums[i])//同层不允许使用重复的数字
{
continue;
}
used[i]=true;
path.push_back(nums[i]);
backtracking(used, i+1, nums, k);
path.pop_back();
used[i]=false;
}
}
};
leetcode 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]。
给定数组中可能包含重复数字,相等的数字应该被视为递增的一种情况。
同层不可使用重复数字(不可排序情况)使用uset数组记录本层使用过的元素去重(used另一种情况)
class Solution {
public:
//类似 不可重复选择 递归深度不限制 子集问题
vector<vector<int>> res;
vector<int> path;
vector<vector<int>> findSubsequences(vector<int>& nums) {
backtracing(nums, 0);
return res;
}
void backtracing (vector<int >& nums,int start)
{
if(path.size()>1)
{
res.push_back(path);//不加return 因为要遍历整棵树,所以不设立递归终止条件,遍历完了自然终止
}
//因为无法事先排序,所以无法采取used[i-1]=false这种来判断
int uset[201]={0};//用数组去重,保证同一层中没有重复取值
for(int i=start;i<nums.size();i++)
{
if((!path.empty()&&nums[i]<path.back())||uset[nums[i]+100]!=0 )//同层不选择该元素原因
continue;
uset[nums[i]+100]=1;
path.push_back(nums[i]);
backtracing(nums, i+1);
path.pop_back();
}
}
};
分割问题
leetcode 131 分割回文串
给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串 。返回 s 所有可能的分割方案。
回文串 是正着读和反着读都一样的字符串。
类似无重复选择 不限制递归深度 的组合问题
class Solution {
public:
//回溯深度不限,无重复选择
//回溯终止条件:startindex==s.length();
vector<string> path;
vector<vector<string>> res;
vector<vector<string>> partition(string s) {
backtracking(0, s);
return res;
}
void backtracking(int startindex,string s)
{
if(startindex==s.length())//递归结束条件:分割结束
{
res.push_back(path);
return;
}
for(int i=startindex;i<s.length();i++)
{
if(!isvalid(startindex, i, s))//本层不选择条件:非回文串
continue;
path.push_back(s.substr(startindex,i-startindex+1));
backtracking(i+1, s);
path.pop_back();
}
}
bool isvalid (int start,int
end,string s)//左右都闭[]
{
for(int i=start,j=end;i<j;i++,j--)
{
if(s[i]!=s[j])
return false;
}
return true;
}
};
- 复原 IP 地址
给定一个只包含数字的字符串,用以表示一个 IP 地址,返回所有可能从 s 获得的 有效 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”]
示例 2:
输入:s = “0000”
输出:[“0.0.0.0”]
示例 3:
输入:s = “1111”
输出:[“1.1.1.1”]
示例 4:
输入:s = “010010”
输出:[“0.10.0.10”,“0.100.1.0”]
示例 5:
输入:s = “101023”
输出:[“1.0.10.23”,“1.0.102.3”,“10.1.0.23”,“10.10.2.3”,“101.0.2.3”]
class Solution {
public:
vector<string> res;
vector<string> path;
vector<string> restoreIpAddresses(string s) {
if(s.length()>12)
return res;
backtracking(0, s, 0);
return res;
};
void backtracking(int startindex,string&s,int k)//
{
//在进行到第三层的时候来做控制,不如从for循环中直接做控制好
if(k==3)
{
if(isvalid(startindex,s.length()-1,s))
{
// cout<<"startindex"<<startindex<<endl;
// cout<<"s.length"<<s.length()<<endl;
string m=s.substr(startindex,s.length()-startindex);//注意不要写.push_back因为这样就破坏了回溯法
// cout<<m;
string a;
for(int i=0;i<3;i++)
{
a=a+path[i]+'.';
}
res.push_back(a+m);
}
}
for(int i=startindex;i<s.length();i++)
{
if(!isvalid(startindex, i, s))
break;
path.push_back(s.substr(startindex,i-startindex+1));
k++;
backtracking(i+1, s, k);
k--;
path.pop_back();
}
}
bool isvalid(int start,int end,string&s)
{ if (start > end) {
return false;
}
if (s[start] == '0' && start != end) { // 0开头的数字不合法
return false;
}
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) { // 如果大于255了不合法
return false;
}
}
return true;
}
};