力扣第40题 组合总和 || c++ 回溯经典

文章讲述了如何使用回溯算法解决组合总和II问题,即给定候选元素数组和目标值,找到所有和为目标值的组合,且每个数字仅能使用一次,避免重复组合。算法通过递归和剪枝优化,时间复杂度为O(2^n*n),空间复杂度为O(n)。
摘要由CSDN通过智能技术生成

题目

40. 组合总和 II

中等

相关标签

数组   回溯

给定一个候选人编号的集合 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 <= candidates.length <= 100
  • 1 <= candidates[i] <= 50
  • 1 <= target <= 30

思路和解题方法

  1. 首先定义了一个二维向量ans用于存储所有满足条件的组合结果,以及一个一维向量path用于存储当前路径。
  2. 定义了一个backtracking函数,该函数通过递归的方式进行回溯操作。参数包括:候选元素数组candidates,目标值target,当前路径和sum,开始索引startindex,以及一个布尔型向量used用于标记元素是否被使用过。
  3. backtracking函数中,首先判断当前路径和是否等于目标值,如果等于,则将该路径添加到结果中,并返回。
  4. 然后通过一个循环遍历候选元素数组,从开始索引startindex开始遍历。在遍历过程中,如果遇到重复的元素且前一个元素未被使用过,则跳过当前元素,以避免生成重复的组合。
  5. 对于每个满足条件的元素,将其加入路径中,并将其标记为已使用。然后递归调用backtracking函数,进入下一层,更新参数:目标值为target减去当前元素,当前路径和为sum加上当前元素,起始索引为当前元素的下一个位置,以及更新了使用状态的布尔型向量used
  6. 当递归返回时,表示已经找到了所有满足条件的组合,需要进行回溯操作。即将当前元素的状态重置为未使用,将路径和减去当前元素,以及移除路径中的当前元素。
  7. 最后,在主函数combinationSum2中,首先定义一个布尔型向量used用于标记元素是否被使用过,并清空路径和结果向量。然后对候选元素数组进行排序,以便在回溯过程中剪枝操作。最后,调用backtracking函数开始解决问题,并返回结果向量ans

复杂度

        时间复杂度:

                O(2^n * n)

        在回溯算法中,每个元素都有两种选择:选择当前元素或者不选择当前元素。因此,在最坏情况下,需要遍历所有可能的组合,这样的组合总数为2^n。而在每个组合中,需要花费线性的时间将路径添加到结果中,因此乘以n。

        空间复杂度

                O(n)

        空间复杂度方面,使用了ans、path和used三个额外的数据结构来辅助求解。其中ans和path的空间复杂度为O(n),used的空间复杂度也为O(n)。因此,整体的空间复杂度为O(n)。

c++ 代码

class Solution {
public:
    vector<vector<int>> ans; // 用于存储结果的二维向量
    vector<int> path; // 用于存储当前路径的一维向量

    void backtracking(vector<int> candidates, int target, int sum, int startindex, vector<bool> used) {
        if (sum == target) { // 当路径和等于目标值时,将当前路径添加到结果中
            ans.push_back(path);
            return;
        }

        for (int i = startindex; i < candidates.size() && sum + candidates[i] <= target; i++) {
            if (i > 0 && candidates[i] == candidates[i - 1] && used[i - 1] == false) {
                continue; // 遇到重复的元素,并且前一个元素未被使用过,则跳过当前元素
            }

            sum += candidates[i]; // 加上当前元素
            path.push_back(candidates[i]); // 将当前元素添加到路径中
            used[i] = true; // 标记当前元素已被使用
            backtracking(candidates, target, sum, i + 1, used); // 递归调用下一层,startIndex为i+1,表示下一个元素
            used[i] = false; // 回溯,将当前元素重置为未使用状态
            sum -= candidates[i]; // 回溯,减去当前元素
            path.pop_back(); // 回溯,移除当前元素
        }
    }

    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        vector<bool> used(candidates.size(), false); // 用于标记元素是否被使用过的向量
        path.clear(); // 清空路径向量
        ans.clear(); // 清空结果向量
        sort(candidates.begin(), candidates.end()); // 对候选元素进行排序
        backtracking(candidates, target, 0, 0, used); // 调用回溯函数
        return ans; // 返回结果向量
    }
};

c++优化代码

  1. 首先,在主函数combinationSum2中,对候选元素数组进行排序,以确保相同的元素都挨在一起。这样做的目的是为了在回溯过程中剪枝,避免生成重复的组合。

  2. 在回溯函数backtracking中,通过判断当前元素是否和前一个元素相同来决定是否跳过该元素。如果当前元素和前一个元素相同且前一个元素未被使用过,则跳过当前元素。这样可以避免在同一树层重复使用相同的元素,从而避免生成重复的组合。

  3. 另外,这个版本的代码还有一个区别是每个数字在每个组合中只能使用一次。这通过在递归调用中将起始索引startIndex设为i + 1来实现。

class Solution {
private:
    vector<vector<int>> result; // 保存最终的结果
    vector<int> path; // 保存当前的组合路径

    void backtracking(vector<int>& candidates, int target, int sum, int startIndex) {
        if (sum == target) { // 如果组合的和等于目标值,则将当前路径加入结果集
            result.push_back(path);
            return;
        }

        for (int i = startIndex; i < candidates.size() && sum + candidates[i] <= target; i++) {
            if (i > startIndex && candidates[i] == candidates[i - 1]) { // 剪枝操作,防止生成重复组合
                continue; // 当前元素和前一个元素相同,且前一个元素未被使用过,则跳过当前元素
            }

            sum += candidates[i]; // 将当前元素加入组合和中
            path.push_back(candidates[i]); // 将当前元素加入组合路径中
            backtracking(candidates, target, sum, i + 1); // 递归调用,起始索引为i+1,保证每个元素在每个组合中只能使用一次
            sum -= candidates[i]; // 回溯,将当前元素从组合和中去除
            path.pop_back(); // 回溯,将当前元素从组合路径中去除
        }
    }

public:
    vector<vector<int>> combinationSum2(vector<int>& candidates, int target) {
        path.clear(); // 清空路径
        result.clear(); // 清空结果集

        sort(candidates.begin(), candidates.end()); // 对候选元素数组进行排序,确保相同元素挨在一起
        backtracking(candidates, target, 0, 0); // 进行回溯操作,得到符合条件的组合

        return result; // 返回最终的结果
    }
};

觉得有用的话可以点点赞,支持一下。

如果愿意的话关注一下。会对你有更多的帮助。

每天都会不定时更新哦  >人<  。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值