回溯算法刷题合集

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>=sizeif(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

刷题方法:回溯法

LeetCode代码模板,刷题必会

面试常考算法题---回溯法(学习笔记)

LeetCode刷题计划

LeetCode刷题计划