回溯算法刷题合集
Posted 落禅
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了回溯算法刷题合集相关的知识,希望对你有一定的参考价值。
回溯算法刷题合集
组合问题
77. 组合
给定两个整数 n 和 k,返回 1 … n 中所有可能的 k 个数的组合。
示例:
输入: n = 4, k = 2
输出:
[
[2,4],
[3,4],
[2,3],
[1,2],
[1,3],
[1,4],
]
//回溯算法求组合问题:
//1.首先构建数组存放要返回的值
//2.判断递归结束的条件
//3.for循环遍历(注意看清楚从哪个位置开始遍历)
//4.递归
//5.回溯
//回溯相当于进行n层for循环
class Solution {
public:
vector<int>path;
vector<vector<int>>ret;
void backtracking(int n,int k,int j)
{
//1.确定终止条件
if(path.size()==k)
{
ret.push_back(path);
return ;
}
//2.for循环+递归相当于进行n层for循环
//每次递归完之后记着回退到上一个位置
for(int i=j;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;
}
};
216. 组合总和 III
找出所有相加之和为 n 的 k 个数的组合。组合中只允许含有 1 - 9 的正整数,并且每种组合中不存在重复的数字。
说明:
所有数字都是正整数。
解集不能包含重复的组合。
示例 1:
输入: k = 3, n = 7
输出: [[1,2,4]]
示例 2:
输入: k = 3, n = 9
输出: [[1,2,6], [1,3,5], [2,3,4]]
本题与上面的77题组合问题非常相似,就是组合问题加上限制条件即可,这次不光要path.size==k,还要使path数组的和为n,因此我们只要对上面的组合的代码加一些限制即可
class Solution {
public:
//定义一个二维数组ret用来存放最终要返回的结果
vector<vector<int>>ret;
//path一位数组用来存放每次遍历的值
vector<int>path;
//定义回溯函数backtracking,startIndex代表每次开始时的下标,k为数字个数,m为和,sum为数组path的各个元素之和
void backtrack(int startIndex,int k,int n,int sum)
{
//与基础组合不同,这里需要强化条件,数量相等时,则进一步判断path的大小与题目要求的大小是否相等
if(path.size()==k)
{
//如果path数组的大小与题目要求的数组大小相同,则将该数组尾插到ret中
if(sum==n)
{
ret.push_back(path);
}
//不相等或者尾插后return,返回到上一层
return;
}
//回溯算法的经典环节,for循环环节,
for(int i=startIndex;i<=9;i++)
{
//每次计算sum的值
sum+=i;
//如果sum>n,直接返回,枝剪
if(sum>n)
break;
//入值
path.push_back(i);
//递归
backtrack(i+1,k,n,sum);
回溯
sum-=i;
path.pop_back();
}
}
vector<vector<int>> combinationSum3(int k, int n) {
path.clear();
ret.clear();
backtrack(1,k,n,0);
return ret;
}
};
39. 组合总和
给定一个无重复元素的数组 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。
candidates 中的数字可以无限制重复被选取。
说明:
所有数字(包括 target)都是正整数。
解集不能包含重复的组合。 g
示例 1:
输入:candidates = [2,3,6,7], target = 7,
所求解集为:
[
[7],
[2,2,3]
]
示例 2:
输入:candidates = [2,3,5], target = 8,
所求解集为:
[
[2,2,2,2],
[2,3,3],
[3,5]
]
求出数组中和为target的元素:即循环遍历数组中的元素,找到和为target的元素,注意这一次startIndex从i开始取,即当前元素可以取重复的,然后注意递归终止条件的判断,本次递归终止条件为sum>target终止,当sum==target时,让path进入到ret数组中
//组合问题的套路:回溯算法
//1.确定函数参数
//2.确定递归终止条件
//3.for循环进行回溯
class Solution {
public:
vector<vector<int>>ret;
vector<int>path;
void backtrack(vector<int>&candidates,int target,int startIndex,int sum)
{
//当target==sum时,返回
if(sum>target)
return;
if(target==sum)
{
ret.push_back(path);
return;
}
for(int i=startIndex;i<candidates.size();i++)
{
sum+=candidates[i];
//sum>taarge时,直接剪枝进行返回
path.push_back(candidates[i]);
//递归回溯过程
backtrack(candidates,target,i,sum);
path.pop_back();
sum-=candidates[i];
}
}
vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
path.clear();
ret.clear();
backtrack(candidates,target,0,0);
return ret;
}
};
17. 电话号码的字母组合
给定一个仅包含数字 2-9
的字符串,返回所有它能表示的字母组合。答案可以按 任意顺序 返回。
给出数字到字母的映射如下(与电话按键相同)。注意 1 不对应任何字母。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xz2Digw8-1630925647001)(https://raw.githubusercontent.com/qingyan520/Cloud_img/master/img/image-20210626132625081.png)]
示例 1:
输入:digits = "23"
输出:["ad","ae","af","bd","be","bf","cd","ce","cf"]
示例 2:
输入:digits = ""
输出:[]
示例 3:
输入:digits = "2"
输出:["a","b","c"]
本题如果不用回溯算法将会非常难算
class Solution {
public:
//首先利用哈希映射将2到9映射到一个二维数组中
string stringMap[10]={"","","abc","def","ghi","jkl","mno","pqrs","tuv","wxyz"};
vector<string>ret;
string path;
void backtrack(string&digits,int index)
{
if(digits.size()==path.size())
{
ret.push_back(path);
return;
}
int n=digits[index]-48;
string letter=stringMap[n];
for(int i=0;i<letter.size();i++)
{
path.push_back(letter[i]);
backtrack(digits,index+1);
path.pop_back();
}
}
vector<string> letterCombinations(string digits) {
if(digits.size()==0)
return ret;
backtrack(digits,0);
return ret;
}
};
131. 分割回文串
给你一个字符串
s
,请你将s
分割成一些子串,使每个子串都是 回文串 。返回s
所有可能的分割方案。回文串 是正着读和反着读都一样的字符串。
示例 1:
输入:s = "aab"
输出:[["a","a","b"],["aa","b"]]
示例 2:
输入:s = "a"
输出:[["a"]]
本题思路:将字符串分割,找出所有的子串,然后判断字串是否为回文字符串,如果是,那么就加入到结果中,那么本题的关键就是遍历分割这个字符串,找出所有的子串,首先我们想到的就是for循环遍历找到所有的子串,但是这好像有点不现实,如果是n层for循环很难进行循环遍历,所以我们这里采取回溯的方式进行循环遍历,找到所有的子串
这道题目的难点:
1.如何切割遍历找到整个子串2.字串的范围该如何去找:【startIndex,i-startIndex+1】,从startIndex到后面i-startIndex+1个元素
3.判断是否为回文字符串:双指针遍历,非常简单
4.递归结束条件的判断:startIndex>=size就代表一层循环进行到了末尾,则返回上一层循环
ps:for循环就像是对第一层循环一样,对每一个位置进行遍历,确定起始切割位置,而里面的递归就像是第二层for循环一样从下一个位置开始进行决定是否进行切割遍历,这个递归下来就像是n层for循环一样遍历找到字串的位置
//首先要进行切割找到每一个子串:比如aab
//它的子串有a,a,b,aa,b,a,ab这些就是它的子串,那么现在就要判断这些子串是不是回文串了
//如果这些子串是回文串,那么就进入返回的结果中,如果不是回文串那么就进行下一次循环
//那么现在要思考得分就是如何进行切割子串问题了,这这里首先想到的就是for循环遍历,n层for循环
//找到每一个子串,然后判断是否是回文串,但是n层for循环好像并不是很好写,所以我们要改变思路
//采用回溯算法,for循环用来进入下一层,递归用来在这一层进行循环
//那么这里还要考虑该如何考虑切割子串,这就要用到stl的库函数,s.substr(begin,end)
//这样就成功拿到了begin到end区间内的子串
//还有一个就是判断回文串了,这个比较简单,就不细说了
class Solution {
public:
//首先创建变量保存每次切割的符合要求的子字符串
vector<vector<string>>ret;
vector<string>path;
//双指针法判断是否为回文串
bool IsPalindrome(string&str)
{
int left=0,right=str.size()-1;
while(left<right)
{
if(str[left]!=str[right])
{
return false;
}
left++;
right--;
}
return true;
}
//回溯暴力找到每一个子串,比较这个子串是否为回文串
void backtrack(string&s,int startIndex)
{
//递归结束的条件是starIndex>=size
if(startIndex>=s.size())
{
ret.push_back(path);
return ;
}
//这个for循环决定了这个递归的深度
for(int i=startIndex;i<s.size();i++)
{
//切割每个子串
string str=s.substr(startIndex,i-startIndex+1);
if(IsPalindrome(str))
{
path.push_back(str);
}
//如果子串不是则进入下个位置判断是否为子串
else
{
continue;
}
//回溯
backtrack(s,i+1);
path.pop_back();
}
}
vector<vector<string>> partition(string s) {
backtrack(s,0);
return ret;
}
};
78. 子集
给你一个整数数组 nums ,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集 不能 包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
示例 2:
输入:nums = [0]
输出:[[],[0]]
这种题目我们首先想到的就是for循环遍历,找到每一个子集,但是不好遍历,故我们采取回溯的方法,暴力枚举出所有的子集可能结果
//求自己问题典型的回溯问题
class Solution {
public:
vector<int>path;
vector<vector<int>>ret;
void backtrack(vector<int>&nums,int startIndex)
{
if(startIndex>=nums.size())
{
return;
}
for(int i=startIndex;i<nums.size();i++)
{
path.push_back(nums[i]);
ret.push_back(path);
backtrack(nums,i+1);
path.pop_back();
}
}
vector<vector<int>> subsets(vector<int>& nums) {
ret.clear();
path.clear();
ret.push_back(path);
backtrack(nums,0);
return ret;
}
};
以1 ,2,3为例,分析上面的代码:首先第一个我们先选1,path=1,ret就为[[1]]然后进入下一层,选2,path=1,2,ret=[[1],[1,2]],然年后进入第三层path=[1,2,3],ret=[[1],[1,2],[1,2,3]],然后一直往上进行返回,直到path清理完,然后又进行第二层
令startIndex=1,开始的二次循环path=2,ret=[[1],[1,2],[1,2,3],[2]],然后进入下一层递归path=[2,3],.ret=[[1],[1,2],[1,2,3],[2],[2,3]],然后又逐层返回清理,使path=null
之后startIndext=2,开始第三次循环,path=3,ret=[[1],[1,2],[1,2,3],[2],[2,3],[3]],
这下就遍历完所有的子集了,当然我们不能忘记还有一个子集是null,故最终的ret=[[1],[1,2],[1,3][1,2,3],[2],[2,3],[3],[]],
排列问题
解决排列问题的一般步骤,首先确定终止条件,for循环中单层循环的逻辑,排列问题一般不需要startIndex来使之循环遍历,而需要used数组标记该元素是否已经被使用,如果没有被使用那么就使用该元素,使用了则循环进入下一层
46. 全排列
给定一个不含重复数字的数组 nums
,返回其 所有可能的全排列 。你可以 按任意顺序 返回答案。
示例 1:
输入:nums = [1,2,3]
输出:[[1,2,3],[1,3,2],[2,1,3],[2,3,1],[3,1,2],[3,2,1]]
示例 2:
输入:nums = [0,1]
输出:[[0,1],[1,0]]
示例 3:
输入:nums = [1]
输出:[[1]]
本题看起来非常的简单,就是一个简单的组合排列问题,但是写起来可不是那么简单,首先我们可以想到n层for循环,但是根本不行,因此换一种遍历方式,回溯法暴力遍历,解决排列问题时首先定义一个used数组,标记已经使用过的数字,used[i]1为true表示该元素已经被使用过,遍历下一个元素,注意排列问题不再需要startIndex来标记下一个位置,终止条件变为了判断数组大小是否相等,
class Solution {
public:
//创建数组用来储藏最终结果
vector<int>path;
vector<vector<int>>ret;
//回溯算法
void backtrack(vector<int>&nums,vector<bool>&used)
{
//当path数组的大小和nums数组的大小相等时说明一个集合已经收集好,将这个集合纳入最终的结果当中去
if(path.size()==nums.size())
{
ret.push_back(path);
return;
}
//单层搜索的逻辑,每次都从一个开始遍历,判断这个元素有没有被使用过,如果被使用过,那么就进入下一层,没有使用过就使用
for(int i=0;i<nums.size();i++)
{
//该元素被使用过就进入下一层
if(used[i]==true)
leetcode之回溯刷题总结1