回溯总结

回溯

1. 组合问题

  1. 组合问题 每次for都是从startIndex开始

  2. 每个元素 用一次和用多次体现在 backtrack(i+1)用一次还是backtrack(i)用多次

  3. 组合问题不需要used数组,去重也不需要used数组那个判断

77. n元素下的k元素组合

labuladong 题解思路

给定两个整数 nk,返回范围 [1, n] 中所有可能的 k 个数的组合。

你可以按 任何顺序 返回答案。

示例 1:

 输入:n = 4, k = 2
 输出:
 [
  [2,4],
  [3,4],
  [2,3],
  [1,2],
  [1,3],
  [1,4],
 ]

示例 2:

 输入:n = 1, k = 1
 输出:[[1]]
思路
  1. 按回溯模板 直接写

  2. 注意剪枝操作, 超过k个元素不再进入递归 24ms->8ms

代码
 class Solution {
 public:
     int n;
     int k;
     vector<int> path;
     vector<vector<int>> all;
     vector<vector<int>> combine(int n, int k) {
         this->n = n;
         this->k = k;
         path.clear();
         all.clear();
         backtrack(1);
         return all;
    }
     void backtrack(int num){
         if(path.size() == k){
             all.push_back(path);
             return;
        }
  //进行剪枝, 超过k个元素不再进入递归 24ms->8ms
         for(int i = num; i<=n - (k-path.size()) + 1; i++){
             path.push_back(i);
             backtrack(i+1);
             path.pop_back();
        }
    }
 };

39. 组合总和

labuladong 题解思路

给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target ,找出 candidates 中可以使数字和为目标数 target所有 不同组合 ,并以列表形式返回。你可以按 任意顺序 返回这些组合。

candidates 中的 同一个 数字可以 无限制重复被选取 。如果至少一个数字的被选数量不同,则两种组合是不同的。

对于给定的输入,保证和为 target 的不同组合数少于 150 个。

示例 1:

 输入:candidates = [2,3,6,7], target = 7
 输出:[[2,2,3],[7]]
 解释:
 2 和 3 可以形成一组候选,2 + 2 + 3 = 7 。注意 2 可以使用多次。
 7 也是一个候选, 7 = 7 。
 仅有这两种组合。

示例 2:

 输入: candidates = [2,3,5], target = 8
 输出: [[2,2,2,2],[2,3,3],[3,5]]
思路
  1. 注意这个题 单个元素是可以重复使用的 表现在代码上 for循环内 backtrack是i 而不是i+1

  2. sum 和 target 的判断逻辑注意下

代码
 class Solution {
 public:
     vector<vector<int>> ans;
     vector<int> path;
 
     void backtracking(vector<int>& candidates, int index, int target, int sum){
       //这里sum和target的判断逻辑注意下
       if(sum>target) return;
       if(sum == target) {
         ans.push_back(path);
         return;
      }
       for(int i = index; i<candidates.size(); i++){
         path.push_back(candidates[i]);
         backtracking(candidates, i, target, sum + candidates[i]); //注意 这里的i不加1 不然没有数重复的
         path.pop_back();
      }
    }
   
     vector<vector<int>> combinationSum(vector<int>& candidates, int target) {
       backtracking(candidates,0, target, 0);
       return ans;
    }
 };

216. 组合总和 III

labuladong 题解

找出所有相加之和为 nk 个数的组合,且满足下列条件:

  • 只使用数字1到9

  • 每个数字 最多使用一次

返回 所有可能的有效组合的列表 。该列表不能包含相同的组合两次,组合可以以任何顺序返回。

示例 1:

 输入: k = 3, n = 7
 输出: [[1,2,4]]
 解释:
 1 + 2 + 4 = 7
 没有其他符合的组合了。

示例 2:

 输入: k = 3, n = 9
 输出: [[1,2,6], [1,3,5], [2,3,4]]
 解释:
 1 + 2 + 6 = 9
 1 + 3 + 5 = 9
 2 + 3 + 4 = 9
 没有其他符合的组合了。
思路
  1. 结束终止满足两个条件之一 nowSum >= n || path.size() == k即可

  2. 满足条件的结果 nowSum == n && path.size() == k

代码
 class Solution {
 public:
     vector<vector<int>> all;
     vector<int> path;
     int k,n ;
     vector<vector<int>> combinationSum3(int k, int n) {
         this->k = k;
         this->n = n;
         backtrack(1, 0);
         return all;
    }
 
     void backtrack(int cur, int nowSum){
         //if(cur>9) return; //注意这句不能要 不然会 9 45判错 最后cur 10 直接返回掉
         if(nowSum >= n || path.size() == k){
             if(nowSum == n && path.size() == k)
                 all.push_back(path);
             return;
        }
 
         for(int i = cur; i<=9; i++){
             path.push_back(i);
             backtrack(i+1, nowSum + i);
             path.pop_back();
        }
    }
 };

40. 组合总和 II

labuladong 题解

给定一个候选人编号的集合 candidates 和一个目标数 target ,找出 candidates 中所有可以使数字和为 target 的组合。

candidates 中的每个数字在每个组合中只能使用 一次

注意:解集不能包含重复的组合。

 

示例 1:

 输入: candidates = [10,1,2,7,6,1,5], target = 8,
 输出:
 [
 [1,1,6],
 [1,2,5],
 [1,7],
 [2,6]
 ]

示例 2:

 输入: candidates = [2,5,2,1,2], target = 5,
 输出:
 [
 [1,2,2],
 [5]
 ]
思路
  1. 结束终止满足两个条件之一 nowSum >= n || path.size() == k即可

  2. 满足条件的结果 nowSum == n && path.size() == k

代码
 class Solution {
 public:
     //去重操作 数组中包含重复元素 所以要去重
     //注意 并不需要used数组 used数组只在排列中用到?即(for(int i = 0)的时候用到)
     vector<vector<int>> all;
     vector<int> path;
     vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
         //排序让相同的元素挨着
         sort(candidates.begin(), candidates.end());
         backtrack(candidates, target, 0, 0);
         return all;
    }
 
     void backtrack(vector<int>& candidates, int target, int curIndex, int nowSum){
         if(nowSum>target) return;
         if(nowSum == target){
             all.push_back(path);
             return;
        }
         for(int i = curIndex; i<candidates.size(); i++){
             // 经典的去重操作 全排列中也用到了
             if(i>curIndex && candidates[i] == candidates[i-1])
                 continue;
             path.push_back(candidates[i]);
             //这里是i+1,每个数字在每个组合中只能使用一次
             backtrack(candidates, target, i+1,  nowSum+candidates[i]);
             path.pop_back();
        }
    }
 };

 

377. 组合总和 Ⅳ

给你一个由 不同 整数组成的数组 nums ,和一个目标整数 target 。请你从 nums 中找出并返回总和为 target 的元素组合的个数。

题目数据保证答案符合 32 位整数范围。

 

示例 1:

 输入:nums = [1,2,3], target = 4
 输出:7
 解释:
 所有可能的组合为:
 (1, 1, 1, 1)
 (1, 1, 2)
 (1, 2, 1)
 (1, 3)
 (2, 1, 1)
 (2, 2)
 (3, 1)
 请注意,顺序不同的序列被视作不同的组合。

示例 2:

 输入:nums = [9], target = 3
 输出:0
思路
  1. 名为组合 实为排列 解法dp

  2. 回溯超时 dp解答

  3. 记忆化dfs 其实就是dp了吧

暴力回溯代码
 class Solution {
 public:
     int ans;
     int combinationSum4(vector<int>& nums, int target) {
         ans = 0;
         backtrack(nums, 0, target);
         return ans;
    }
 
     void backtrack(vector<int>& nums, int nowSum, int target){
         if(nowSum>target) return;
         if(nowSum == target){
             ans++;
             return;
        }
      //可以反向 就是排列了
         for(int i = 0; i<nums.size(); i++){
             backtrack(nums, nowSum+nums[i], target);
        }
    }
 };
记忆化dfs
 class Solution {
 public:
     int combinationSum4(vector<int>& nums, int target) {
         return dfs(nums, target);
    }
     //备忘录,保存每层递归的计算结果,用于实现记忆化。
     unordered_map<int, int> memo;
     //dfs(target)的定义: 用nums中的元素凑成总和为target(每个元素可以使用多次),用多少中凑法。
     int dfs(vector<int>& nums, int target){
         if(target == 0)
             return 1;
         if(target < 0)
             return 0;
         if(memo.count(target) == 1)
             return memo[target];
         int res = 0;
         for(int i = 0; i < nums.size(); i++){
             res += dfs(nums, target - nums[i]);
        }
         memo[target] = res;
         return res;
    }
 };
dp代码
 class Solution {
 public:
     int combinationSum4(vector<int>& nums, int target) {
         //使用dp数组,dp[i]代表组合数为i时使用nums中的数能组成的组合数的个数
         //dp[i]=dp[i-nums[0]]+dp[i-nums[1]]+dp[i=nums[2]]+...
         //举个例子比如nums=[1,3,4],target=7;
         //dp[7]=dp[6]+dp[4]+dp[3]
         //其实就是说7的组合数可以由三部分组成,1和dp[6],3和dp[4],4和dp[3];
         vector<unsigned long long> dp(target+1);
         //是为了算上自己的情况,比如dp[1]可以由dp【0】和1这个数的这种情况组成。
         dp[0] = 1;
         for(int i = 1; i<=target; i++){
             for(int num : nums){
                 //dp用int的话 有一个很傻逼的越界,需要 && dp[i - num] < INT_MAX - dp[i]
                 if(i>=num)  
                     dp[i] += dp[i-num];
            }
        }
         return dp[target];
    }
 };

 

posted @ 2022-03-15 19:29  千寻slimg  阅读(137)  评论(0编辑  收藏  举报