【力扣-回溯】3、组合总和

145 阅读2分钟

「这是我参与11月更文挑战的第23天,活动详情查看:2021最后一次更文挑战

39. 组合总和

题目描述

给定一个无重复元素的正整数数组 candidates 和一个正整数 target ,找出 candidates 中所有可以使数字和为目标数 target 的唯一组合。

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

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

 

示例 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]]

示例 3:

输入: candidates = [2], target = 1
输出: []

示例 4:

输入: candidates = [1], target = 1
输出: [[1]]

示例 5:

输入: candidates = [1], target = 2
输出: [[1,1]]

解析

回溯法 回溯三部曲

  • 1、确定递归函数与返回值
    • 参数:
      • (1)定义两个全局参数,二维数组result用来存放结果,数组path用来存放符合条件的结果
      • (2)题目给出了参数:候选元素数组 candates , 目标值 target
      • (3)为了方便统计path中存储的元素和,所以还需要一个int类型的sum 来存放path中的和
      • (4)同时还需要一个索引变量 startIndex 来记录 for 循环中每次遍历开始的位置 综上,递归函数的参数有四个:candates , target , sum , startIndex
  • 2、递归终止的条件
    • 有两种情况需要终止递归:
      • (1)sum的值等于 target的值,即 sum == target
      • (2)sum的值大于 target的值,即 sum > target
  • 3、单层循环的逻辑
    • 单层 for 循环从 startIndex 开始,搜索 candates 集合
    • 注意需要进行回溯

代码

class Solution
{
public:
    vector<vector<int>> combinationSum(vector<int> &candates, int target)
    {
        result.clear();
        path.clear();
        backtracking(candates, target, 0, 0);
        return result;
    }

private:
    // 存放结果集
    vector<vector<int>> result;
    // 存放符合条件的元素
    vector<int> path;

    void backtracking(vector<int> &candates, int target, int sum, int startIndex)
    {
        // 终止条件
        if (sum > target)
        {
            return;
        }
        if (sum == target)
        {
            result.push_back(path);
            return;
        }

        // 遍历元素(未进行剪枝操作)
        for (int i = startIndex; i < candates.size(); i++)
        {
            // 处理sum
            sum += candates[i];
            path.push_back(candates[i]);
            // 这里元素可以重复使用,所以可以从i开始
            backtracking(candates, target, sum, i);

            // 回溯
            path.pop_back();
            sum -= candates[i];
        }
    }
};

剪枝优化

首先对candates进行排序,如果下一层的 sum (也就是本层的 sum+candates[i]),如果值大于 target ,就可以结束本轮for循环

剪枝后的完整代码

class Solution
{
public:
    vector<vector<int>> combinationSum(vector<int> &candates, int target)
    {
        result.clear();
        path.clear();
        // 剪枝操作。首先进行排序
        sort(candates.begin(), candates.end());
        backtracking(candates, target, 0, 0);
        return result;
    }

private:
    // 存放结果集
    vector<vector<int>> result;
    // 存放符合条件的元素
    vector<int> path;

    void backtracking(vector<int> &candates, int target, int sum, int startIndex)
    {
        // 终止条件
        if (sum > target)
        {
            return;
        }
        if (sum == target)
        {
            result.push_back(path);
            return;
        }
        
        // 剪枝优化
        // 对总集合进行排序后,如果下一层的 sum (也就是本层的 sum + candates[i])
        // 如果已经大于 target 就就可以结束本轮的for循环
        // for 循环剪枝, 需要先对 candates 进行排序
        for (int i = startIndex; i < candates.size() && sum + candates[i] <= target; i++)
        {
            // 下面的代码与剪枝前的相同
            sum += candates[i];
            path.push_back(candates[i]);
            backtracking(candates, target, sum, i);
            path.pop_back();
            sum -= candates[i];
        }
    }
};