今日算法(回溯子集)(模版题)

题目描述

给你一个整数数组 nums,数组中的元素互不相同。返回该数组所有可能的子集(幂集)。

解集不能 包含重复的子集。你可以按任意顺序返回解集。

示例 1:

复制代码
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]

示例 2:

复制代码
输入:nums = [0]
输出:[[],[0]]

提示:

  • 1 <= nums.length <= 10
  • -10 <= nums[i] <= 10
  • nums 中的所有元素互不相同

解题思路

子集问题是回溯算法的经典入门题目,同时也可以用位运算和迭代法来解决。下面我们将详细讲解这三种方法。

方法一:回溯法

思路分析

回溯法的核心思想是 "选择与不选择"。对于数组中的每一个元素,我们都有两种选择:包含它或者不包含它。通过递归遍历所有可能的选择,我们就能得到所有的子集。

具体来说,我们可以维护一个当前的子集 path,然后从数组的第一个元素开始:

  1. 不选择当前元素,直接递归处理下一个元素
  2. 选择当前元素,将其加入 path,然后递归处理下一个元素
  3. 递归返回后,将当前元素从 path 中移除(回溯)

当我们处理完所有元素时,就将当前的 path 加入结果集。

代码实现(C++)
复制代码
#include <vector>
using namespace std;

class Solution {
private:
    vector<vector<int>> result; // 存储所有子集
    vector<int> path;           // 存储当前子集

    void backtracking(vector<int>& nums, int startIndex) {
        // 将当前子集加入结果集
        result.push_back(path);
        
        // 从startIndex开始遍历,避免重复子集
        for (int i = startIndex; i < nums.size(); i++) {
            path.push_back(nums[i]);          // 选择当前元素
            backtracking(nums, i + 1);        // 递归处理下一个元素
            path.pop_back();                  // 回溯,撤销选择
        }
    }

public:
    vector<vector<int>> subsets(vector<int>& nums) {
        result.clear();
        path.clear();
        backtracking(nums, 0);
        return result;
    }
};
复杂度分析
  • 时间复杂度:O (n × 2ⁿ)。共有 2ⁿ 个子集,每个子集需要 O (n) 的时间来复制到结果集中。
  • 空间复杂度 :O (n)。递归调用栈的深度为 n,同时 path 数组最多存储 n 个元素。

方法二:位运算法

思路分析

对于一个长度为 n 的数组,它的子集总数是 2ⁿ 个。我们可以用一个 n 位的二进制数来表示一个子集,其中每一位表示对应位置的元素是否被包含在子集中。

例如,对于数组 [1,2,3]

  • 二进制 000 表示空集 []
  • 二进制 001 表示子集 [1]
  • 二进制 010 表示子集 [2]
  • 二进制 011 表示子集 [1,2]
  • 以此类推,直到 111 表示子集 [1,2,3]

我们只需要遍历从 0 到 2ⁿ - 1 的所有整数,然后根据每个整数的二进制表示来构造对应的子集即可。

代码实现(C++)
复制代码
#include <vector>
using namespace std;

class Solution {
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        vector<vector<int>> result;
        int n = nums.size();
        int total = 1 << n; // 2^n 个子集

        for (int mask = 0; mask < total; mask++) {
            vector<int> subset;
            for (int i = 0; i < n; i++) {
                // 检查第i位是否为1
                if (mask & (1 << i)) {
                    subset.push_back(nums[i]);
                }
            }
            result.push_back(subset);
        }

        return result;
    }
};
复杂度分析
  • 时间复杂度:O (n × 2ⁿ)。共有 2ⁿ 个子集,每个子集需要 O (n) 的时间来构造。
  • 空间复杂度:O (1)。除了结果集之外,只使用了常数级别的额外空间。

方法三:迭代法

思路分析

迭代法的核心思想是 "逐步构建"。我们从空集开始,然后依次将数组中的每个元素添加到已有的所有子集中,从而生成新的子集。

例如,对于数组 [1,2,3]

  1. 初始状态:[[]]
  2. 添加元素 1:将 1 添加到已有的每个子集中,得到 [[], [1]]
  3. 添加元素 2:将 2 添加到已有的每个子集中,得到 [[], [1], [2], [1,2]]
  4. 添加元素 3:将 3 添加到已有的每个子集中,得到最终结果
代码实现(C++)
复制代码
#include <vector>
using namespace std;

class Solution {
public:
    vector<vector<int>> subsets(vector<int>& nums) {
        vector<vector<int>> result;
        result.push_back({}); // 初始化为包含空集

        for (int num : nums) {
            // 保存当前结果集的大小,避免在循环中修改result导致无限循环
            int size = result.size();
            for (int i = 0; i < size; i++) {
                // 复制已有子集,并添加当前元素
                vector<int> newSubset = result[i];
                newSubset.push_back(num);
                result.push_back(newSubset);
            }
        }

        return result;
    }
};
复杂度分析
  • 时间复杂度:O (n × 2ⁿ)。共有 2ⁿ 个子集,每个子集需要 O (n) 的时间来复制。
  • 空间复杂度:O (1)。除了结果集之外,只使用了常数级别的额外空间。

三种方法对比

表格

方法 优点 缺点 适用场景
回溯法 思路清晰,易于理解,可扩展到有重复元素的子集问题 需要理解递归和回溯的概念 大多数子集、组合、排列问题
位运算法 代码简洁,效率高,不需要递归 思路相对抽象 数组长度较小(n ≤ 20)的情况
迭代法 直观易懂,不需要递归 代码稍长 初学者理解子集生成过程

总结

子集问题是算法中的基础问题,掌握这三种解法对于理解回溯算法、位运算和迭代思想都有很大帮助。

  • 回溯法是解决组合类问题的通用方法,需要重点掌握 "选择 - 递归 - 回溯" 的思想
  • 位运算法利用了二进制的特性,代码非常简洁,在数组长度较小时效率很高
  • 迭代法通过逐步构建的方式生成子集,最容易理解

在实际解题中,推荐优先掌握回溯法,因为它可以很容易地扩展到其他类似问题,如 "子集 II"(数组中有重复元素)、"组合总和" 等。

相关推荐
吴佳浩1 小时前
Vibe Coding 时代,研发经理为何越来越值钱?
算法·架构
IronMurphy1 小时前
【算法五十四】72. 编辑距离
算法
QiLinkOS1 小时前
【用呼吸重构创造价值关系——QiLink生态】
c语言·数据结构·c++·人工智能·单片机·嵌入式硬件·算法
妄想出头的工业炼药师1 小时前
暗光长走廊特殊场景视觉解决方案
算法·开源
weixin_468466851 小时前
图像处理特征提取新手实战指南
图像处理·人工智能·算法·ai·机器视觉·特征提取
weixin_468466851 小时前
图像处理之形态学处理新手实战指南
图像处理·人工智能·算法·ai·机器视觉·形态学
晚风予卿云月2 小时前
【前缀和】一维前缀和 & 二维前缀和
数据结构·c++·算法
YL200404262 小时前
071字符串解码
数据结构·leetcode
林文韬3272 小时前
逐位二进制拼接 → 翻转 → 去头零 → 消邻重
算法