##回溯法##
回溯法总结
1.DFS和回溯算法区别
DFS是一个劲的往某一个方向搜索,而回溯算法建立在DFS基础之上的,但不同的是在搜索过程中,达到结束条件后,恢复状态,回溯上一层,再次搜索。因此回溯算法与DFS的区别就是有无状态重置
2.何时使用回溯算法
** 当问题需要"回头",以此来查找出所有的解的时候**,使用回溯算法。即满足结束条件或者发现不是正确路径的时候(走不通),要撤销选择,回退到上一个状态,继续尝试,直到找出所有解为止
3.怎么样写回溯算法(从上而下,※代表难点)
①画出递归树,找到状态变量(回溯函数的参数),这一步非常重要※
②根据题意,确立结束条件
③找准选择列表(与函数参数相关),与第一步紧密关联※
④判断是否需要剪枝
⑤作出选择,递归调用,进入下一层
⑥撤销选择
4.回溯问题的类型
类型 | 题目链接 |
---|---|
子集、组合 | 子集、子集 II、组合、组合总和、组合总和 II |
全排列 | 全排列、全排列 II、字符串的全排列、字母大小写全排列 |
搜索 | 解数独、单词搜索、N皇后、分割回文串、二进制手表 |
4.1子集、组合
子集
给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
**说明:**解集不能包含重复的子集。
输入: nums = [1,2,3] 输出: [ [3], [1], [2], [1,2,3], [1,3], [2,3], [1,2], [] ]
class Solution {
public:
vector<vector<int>> res;
void backtrack(vector<int>& nums,vector<int>& path,int index){
res.push_back(path);
for (int i = index; i <nums.size() ; ++i) {
path.push_back(nums[i]);
backtrack(nums,path,i+1);
path.pop_back();
}
}
vector<vector<int>> subsets(vector<int>& nums) {
vector<int> path{};
backtrack(nums,path,0);
return res;
}
};
子集2
给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。
**说明:**解集不能包含重复的子集。
输入: [1,2,2] 输出: [ [2], [1], [1,2,2], [2,2], [1,2], [] ]
加了剪枝
class Solution {
public:
vector<vector<int>> res;
void backtrack(vector<int>& nums,vector<int>& path,int index){
res.push_back(path);
for (int i = index; i <nums.size() ; ++i) {
if(i>index && nums[i-1]==nums[i])
continue;
path.push_back(nums[i]);
backtrack(nums,path,i+1);
path.pop_back();
}
}
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
vector<int> path{};
backtrack(nums,path,0);
return res;
}
};
组合总和
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。输入: candidates = [2,3,6,7], target = 7, 所求解集为: [ [7], [2,2,3] ] 输入: candidates = [2,3,5], target = 8, 所求解集为: [ [2,2,2,2], [2,3,3], [3,5] ]
class Solution {
public:
vector<vector<int>> res;
void dfs(vector<int>& candidates,vector<int>& path, int index,int sum,int target){
if(sum==target){
res.push_back(path);
return;
}
for (int i = index; i <candidates.size() ; ++i) {
if(sum>target)
continue;
path.push_back(candidates[i]);
dfs(candidates,path,i,sum+candidates[i],target);
path.pop_back();
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
vector<int> path;
dfs(candidates,path,0,0,target);
set<vector<int>> temp(res.begin(),res.end());
res.assign(temp.begin(),temp.end());
return res;
}
};
分割回文串 嘿嘿
给定一个字符串 s,将 s 分割成一些子串,使每个子串都是回文串。
返回 s 所有可能的分割方案。
输入: "aab" 输出: [ ["aa","b"], ["a","a","b"] ]
class Solution {
public:
vector<vector<string>> res;
void backtrack(string s,vector<string>& parts,int index){
if(index==s.size()){
res.push_back(parts);
return;
}
string temp1;
for (int i = index; i <s.size() ; ++i) {
temp1+=s[i];
string temp2=temp1;
reverse(temp2.begin(),temp2.end());
if(temp2==temp1){
parts.push_back(temp1);
backtrack(s,parts,i+1);
parts.pop_back();
}
}
}
vector<vector<string>> partition(string s) {
vector<string> parts;
backtrack(s,parts,0);
return res;
}
};
电话号码的字母组合
给定一个仅包含数字
2-9
的字符串,返回所有它能表示的字母组合。给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-e1XhLRhc-1595485861554)(C:\Users\86178\Desktop\力扣\图片\2020-05-21_213228.png)]
自己写的
class Solution {
public:
unordered_map<char,string> map{
{'2',"abc"},{'3',"def"},{'4',"ghi"},{'5',"jkl"},{'6',"mno"},{'7',"pqrs"},{'8',"tuv"},{'9',"wxyz"}
};
vector<string> res;
void tracbeck(string digits,int index,string& path){
if(index==digits.size()){
if(path.size()==digits.size())
res.push_back(path);
return;
}
string words;
for (int i = index; i <digits.size() ; ++i) {
words=map[digits[index]];
for (int j = 0; j < words.size(); ++j) {
path+=words[j];
tracbeck(digits,i+1,path);
path.erase(path.end()-1);
}
}
}
vector<string> letterCombinations(string digits) {
if(digits=="")
return res;
string path;
tracbeck(digits,0,path);
return res;
}
};
看了题解优化了
class Solution {
public:
unordered_map<char,string> map{
{'2',"abc"},{'3',"def"},{'4',"ghi"},{'5',"jkl"},{'6',"mno"},{'7',"pqrs"},{'8',"tuv"},{'9',"wxyz"}
};
vector<string> res;
string path;
void tracbeck(string digits,int index){
if(index==digits.size()){
res.push_back(path);
return;
}
for (int j = 0; j < map[digits[index]].size(); ++j) {
path.push_back(map[digits[index]][j]);
tracbeck(digits,index+1);
path.pop_back();
}
}
vector<string> letterCombinations(string digits) {
if(digits.size()==0)
return res;
tracbeck(digits,0);
return res;
}
};
无重复字符串的排列组合
无重复字符串的排列组合。编写一种方法,计算某字符串的所有排列组合,字符串每个字符均不相同。
输入:S = "qwe" 输出:["qwe", "qew", "wqe", "weq", "ewq", "eqw"]
标准回溯
class Solution {
public:
vector<string> res;
void backtrack(string S,string& path,vector<int>& flag,int index){
if(index==S.size()){
res.push_back(path);
return;
}
for (int i = 0; i < S.size(); ++i) {
if(flag[i])
continue;
path.push_back(S[i]);
flag[i]=1;
backtrack(S,path,flag,index+1);
path.pop_back();
flag[i]=0;
}
}
vector<string> permutation(string S) {
string path;
vector<int> flag(S.size(),0);
backtrack(S,path,flag,0);
return res;
}
};
回溯2
class Solution {
public:
vector<string> res;
void backtrack(string S,int index){
if(index==S.size()){
res.push_back(S);
return;
}
for (int i = index; i <S.size() ; ++i) {
string temp=S;
swap(temp[index],temp[i]);
backtrack(temp,index+1);
}
}
vector<string> permutation(string S) {
string path;
backtrack(S,0);
return res;
}
};
无重复字符串的排列组合2
有重复字符串的排列组合
使用set去重
class Solution {
public:
set<string> res;
void backtrack(string S,string& path,vector<int>& flag){
if(path.size()==S.size()){
res.insert(path);
return;
}
for (int i = 0; i < S.size(); ++i) {
if(flag[i]==0){
path.push_back(S[i]);
flag[i]=1;
backtrack(S,path,flag);
path.pop_back();
flag[i]=0;
}
}
}
vector<string> permutation(string S) {
string path;
vector<int> flag(S.size(),0);
backtrack(S,path,flag);
vector<string> temp(res.begin(),res.end());
return temp;
}
};
剪枝去重
思路
多了一个排序和剪枝函数 排序使得重复的在一起 剪枝讲解: 1、因为i-1的存在 所以i>0 2、S[i]==S[i-1] && flag[i-1]==0表示这个i的子树i-1都经历过了 且是刚经历 在递归树中可以看出这个特点 if(i>0 && S[i]==S[i-1] && flag[i-1]==0) continue;
class Solution {
public:
vector<string> res;
void backtrack(string S,string& path,vector<int>& flag,int index){
if(index==S.size()){
res.push_back(path);
return;
}
for (int i = 0; i < S.size(); ++i) {
if(flag[i])
continue;
if(i>0 && S[i]==S[i-1] && flag[i-1]==0)
continue;
path.push_back(S[i]);
flag[i]=1;
backtrack(S,path,flag,index+1);
path.pop_back();
flag[i]=0;
}
}
vector<string> permutation(string S) {
string path;
vector<int> flag(S.size(),0);
sort(S.begin(),S.end());
backtrack(S,path,flag,0);
return res;
}
};
4.2搜索
二进制手表顶部有 4 个 LED 代表小时(0-11),底部的 6 个 LED 代表分钟(0-59)。
每个 LED 代表一个 0 或 1,最低位在右侧。
给定一个非负整数 n 代表当前 LED 亮着的数量,返回所有可能的时间。
输入: n = 1 返回: ["1:00", "2:00", "4:00", "8:00", "0:01", "0:02", "0:04", "0:08", "0:16", "0:32"] 注意事项: 输出的顺序没有要求。 小时不会以零开头,比如 “01:00” 是不允许的,应为 “1:00”。 分钟必须由两位数组成,可能会以零开头,比如 “10:2” 是无效的,应为 “10:02”。
思路:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-rd0rcmW5-1595485861557)(C:\Users\86178\Desktop\力扣\图片\2020-05-20_144911.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-iv6xggWh-1595485861558)(C:\Users\86178\Desktop\力扣\图片\2020-05-20_144926.png)]
class Solution {
public:
vector<string> res;
unordered_map<int ,int> hash={
{0,1},{1,2},{2,4},{3,8},{4,1},{5,2},{6,4},{7,8},{8,16},{9,32}
};
void backtrak(int num,int index,pair<int,int>& time){
if(num==0){
if(time.first>11 || time.second>59)
return;
string hour=to_string(time.first);
string minute=to_string(time.second);
if(minute.size()==1)
minute.insert(0,"0");
res.push_back(hour+":"+minute);//构造格式
}
for (int i = index; i <10 ; ++i) {
if(time.first>11 || time.second>59)
continue;
pair<int,int> temp=time;//保存状态
if(i<4)
time.first+=hash[i];
else
time.second+=hash[i];
backtrak(num-1,i+1,time);//进入下一层,注意下一层的start是i+1,即从当前灯的下一盏开始
time=temp;//恢复状态
}
}
vector<string> readBinaryWatch(int num) {
pair<int,int> time(0,0);
backtrak(num,0,time);
return res;
}
};
22. 括号生成
数字 n 代表生成括号的对数,请你设计一个函数,用于能够生成所有可能的并且 有效的 括号组合。
示例:
输入:n = 3
输出:[
“((()))”,
“(()())”,
“(())()”,
“()(())”,
“()()()”
]
深搜
class Solution {
public:
vector<string> res;
void dfs(string path, int n, int lc, int rc){
if(rc>lc || lc>n || rc>n) return;
if(lc==rc && lc==n){
res.push_back(path);
return;
}
dfs(path + '(', n, lc + 1, rc);
dfs(path + ')', n, lc, rc + 1);
}
vector<string> generateParenthesis(int n) {
int lc=0;//左括号个数
int rc=0;//右括号个数
string path="";
dfs(path, n, lc, rc);
return res;
}
};
回溯
class Solution {
public:
vector<string> res;
void backtrack(string& path,int n,int lc,int rc){
if(path.size()==n*2){
res.push_back(path);
return;
}
if(lc<n){
path.push_back('(');
backtrack(path,n,lc+1,rc);
path.pop_back();
}
if(rc<lc){
path.push_back(')');
backtrack(path,n,lc,rc+1);
path.pop_back();
}
}
vector<string> generateParenthesis(int n) {
int lc=0;//左括号个数
int rc=0;//右括号个数
string path="";
backtrack(path,n,lc,rc);
return res;
}
};