回溯总结2(子集问题)

78 子集

给定一组不含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。
示例: 输入: nums = [1,2,3] 输出: [ [3], [1], [2], [1,2,3], [1,3], [2,3], [1,2], [] ]

思路

回溯返回条件,index==num,size()

c++ 复制代码
class Solution {

public:

    vector<vector<int>>res;
    void backtracing(vector<int>& nums,vector<int>& path,int startIndex){
        if(startIndex==nums.size()){
            return;
        }

        for(int i = startIndex;i<nums.size();i++){
            path.push_back(nums[i]);
            res.push_back(path);
            startIndex++;
            backtracing(nums,path,startIndex);
            path.pop_back();
        }
    }

    vector<vector<int>> subsets(vector<int>& nums) {
        vector<int> path;
        int startIndex=0;
        res.push_back(path);
        backtracing(nums,path,startIndex);
        return res;
    }

};

90 子集

给定一个可能包含重复元素的整数数组 nums,返回该数组所有可能的子集(幂集)。

说明:解集不能包含重复的子集。

示例:

  • 输入: [1,2,2]
  • 输出: [ [2], [1], [1,2,2], [2,2], [1,2], [] ]

思路

参考组合总和||,关键是去重

有了index就不需要used数组

c++ 复制代码
class Solution {
public:
    vector<vector<int>> res;
    vector<int> path;
    void dfs(vector<int>& nums, int startIndex) {
        // 每个路径都是一个合法的子集,直接加入结果
        res.push_back(path);
        // 遍历当前可选的数字
        for(int i = startIndex; i < nums.size(); i++) {
            // 跳过重复元素(同一层)
            if(i > startIndex && nums[i] == nums[i-1]) {
                continue;
            }
            // 选择当前数字
            path.push_back(nums[i]);
            // 递归搜索
            dfs(nums, i + 1);
            // 回溯,撤销选择
            path.pop_back();
        }
    }
    vector<vector<int>> subsetsWithDup(vector<int>& nums) {
        sort(nums.begin(), nums.end());  // 排序,便于去重
        dfs(nums, 0);
        return res;
    }
};

491 递增子序列

给定一个整型数组, 你的任务是找到所有该数组的递增子序列,递增子序列的长度至少是2。

示例:

  • 输入: [4, 6, 7, 7]
  • 输出: [[4, 6], [4, 7], [4, 6, 7], [4, 6, 7, 7], [6, 7], [6, 7, 7], [7,7], [4,7,7]]
    说明:
  • 给定数组的长度不会超过15。
  • 数组中的整数范围是 [-100,100]。
  • 给定数组中可能包含重复数字,相等的数字应该被视为递增的一种情况。
    题目要求递增子序列大小至少为2

思路

  1. 剪枝,选出所有组合,然后找到递增序列
    unordered_set<int> uset; 是记录本层元素是否重复使用,新的一层uset都会重新定义(清空),所以要知道uset只负责本层!

注意:

// 用于判断是否可以将当前元素添加到路径中

bool isValid(vector& nums, int i, unordered_set& used) {

return (path.empty() || nums[i] >= path.back()) && !used.count(nums[i]);

}

c++ 复制代码
class Solution {

public:
    vector<vector<int>> res;
    vector<int> path;
    // 用于判断是否可以将当前元素添加到路径中
    bool isValid(vector<int>& nums, int i, unordered_set<int>& used) {
        return (path.empty() || nums[i] >= path.back()) && !used.count(nums[i]);
    }
    void backtracing(vector<int>& nums, int startIndex) {
        if (path.size() >= 2) { // 确保只存储有效长度的路径
            res.push_back(path);
        }

        if (startIndex == nums.size()) return;
        unordered_set<int> used; // 用于记录当前层级访问过的数字
        for (int i = startIndex; i < nums.size(); i++) {
            // 使用封装后的 isValid 函数来判断是否可以继续
            if (!isValid(nums, i, used)) {
                continue;
            }
            used.insert(nums[i]); // 记录当前数字
            path.push_back(nums[i]);
            backtracing(nums, i + 1);
            path.pop_back(); // 回溯
        }
    }

    vector<vector<int>> findSubsequences(vector<int>& nums) {
        res.clear();
        path.clear();
        backtracing(nums, 0);
        return res;
    }
};

1. 布尔数组标记法 (适用于子集问题)

特点

  • 实现简单,空间效率高。
  • 适用于一般的子集/组合问题,特别是对于需要返回所有可能的子集的情况。

举例题目

  • LeetCode 90: Subsets II (子集 II):给定一个可能包含重复元素的整数数组 nums,返回所有可能的子集(包含空集)。需要去重。

题目描述

输入: nums = [1, 2, 2]
输出:
[
  [],
  [1],
  [1,2],
  [2],
  [2,2]
]

2. 计数map法 (适用于元素重复次数较多的情况)

特点

  • 高效处理重复元素较多的情况。
  • 需要额外空间来存储每个元素的计数。

举例题目

  • LeetCode 90: Subsets II (子集 II)和 LeetCode 40: Combination Sum II(组合总和 II):对于包含重复元素的数组,避免重复组合。

题目描述

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

3. 选或不选法 (适用于有限制的组合问题)

特点

  • 思路清晰,适合处理有选择限制的组合问题。
  • 在某些情况下可能实现较复杂。

举例题目

  • LeetCode 78: Subsets:给定一个不含重复元素的整数数组,返回其所有子集。

题目描述

输入: nums = [1, 2, 3]
输出: 
[
  [],
  [1],
  [2],
  [3],
  [1,2],
  [1,3],
  [2,3],
  [1,2,3]
]
  • LeetCode 46: Permutations(排列):给定一个没有重复数字的序列,返回其所有可能的排列。

题目描述

输入: nums = [1, 2, 3]
输出: 
[
  [1, 2, 3],
  [1, 3, 2],
  [2, 1, 3],
  [2, 3, 1],
  [3, 1, 2],
  [3, 2, 1]
]

4. 交换法 (排列专用)

特点

  • 不需要额外数组来记录路径,直接在原数组上进行操作。
  • 适用于排列问题,尤其是要求去重的排列。

举例题目

  • LeetCode 47: Permutations II:给定一个可包含重复数字的序列,返回所有不重复的排列。

题目描述

输入: nums = [1, 1, 2]
输出: 
[
  [1, 1, 2],
  [1, 2, 1],
  [2, 1, 1]
]

总结:

  • 布尔数组标记法:适合标准的子集问题,简单且高效。
  • 计数map法:适合处理重复元素较多的情况,使用额外的计数信息来避免重复。
  • 选或不选法:适合限制条件较为复杂的组合问题,尤其是元素选择有上限时。
  • 交换法:专门用于排列问题,避免重复排列,操作在原数组上进行。

每种方法适用的场景不同,选择时需要考虑问题的性质、元素是否重复以及对时间空间的要求。

相关推荐
汤姆和杰瑞在瑞士吃糯米粑粑31 分钟前
【C++学习篇】滑动窗口--结合例题讲解思路
开发语言·数据结构·c++·算法·散列表
字节高级特工1 小时前
【优选算法】3----快乐数
c++·算法
vir021 小时前
除自身以外数组的乘积(前后积)
算法
一缕叶2 小时前
P8738 [蓝桥杯 2020 国 C] 天干地支
c语言·算法·蓝桥杯
不玩return的马可乐2 小时前
蓝桥杯 单词重排
开发语言·数据结构·c++·算法·leetcode·职场和发展·蓝桥杯
DogDaoDao3 小时前
leetcode 面试经典 150 题:插入区间
c++·算法·leetcode·面试·贪心算法·vector·插入区间
Geometry Fu3 小时前
二叉树删除子树 (题目分析+C++代码实现)
数据结构·算法·图论
一缕叶3 小时前
P8772 [蓝桥杯 2022 省 A] 求和
数据结构·c++·算法
金融OG4 小时前
99.12 金融难点通俗解释:毛利率
python·算法·机器学习·数学建模·金融
夏末秋也凉9 小时前
力扣-数组-283 移动零
算法·leetcode