LeetCode 78. Subsets 题解
题目描述
给你一个整数数组 nums,数组中的元素 互不相同 。返回该数组所有可能的子集(幂集)。
解集不能包含重复的子集。你可以按 任意顺序 返回解集。
示例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
示例 2:
输入:nums = [0]
输出:[[],[0]]
解题思路
方法一:回溯算法
思路:
- 使用回溯算法生成所有可能的子集
- 维护一个路径数组
path来存储当前的子集 - 维护一个起始索引
start,表示从哪个位置开始选择元素,避免重复子集 - 每次递归调用时,将当前
path添加到结果中 - 然后遍历从
start到数组末尾的每个元素:- 将元素添加到
path中 - 递归调用回溯函数,起始索引为当前索引加 1
- 回溯:从
path中移除元素
- 将元素添加到
复杂度分析:
- 时间复杂度:O(n × 2^n),其中 n 是数组的长度。总共有 2^n 个子集,每个子集需要 O(n) 的时间复制到结果中。
- 空间复杂度:O(n),其中 n 是数组的长度。递归调用的栈空间和
path数组的空间都是 O(n)。
方法二:迭代法
思路:
- 从空集开始,逐步构建所有子集
- 对于数组中的每个元素,将其添加到现有的所有子集中,形成新的子集
- 例如,对于数组 [1,2,3]:
- 初始子集:[]
- 添加 1:[], [1]
- 添加 2:[], [1], [2], [1,2]
- 添加 3:[], [1], [2], [1,2], [3], [1,3], [2,3], [1,2,3]
复杂度分析:
- 时间复杂度:O(n × 2^n),其中 n 是数组的长度。总共有 2^n 个子集,每个子集需要 O(n) 的时间处理。
- 空间复杂度:O(n × 2^n),其中 n 是数组的长度。需要存储 2^n 个子集,每个子集的平均长度为 O(n)。
代码实现
方法一:回溯算法
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
result = []
path = []
n = len(nums)
def backtrack(start):
# 将当前 path 添加到结果中
result.append(path.copy())
# 遍历从 start 到数组末尾的每个元素
for i in range(start, n):
# 将元素添加到 path 中
path.append(nums[i])
# 递归调用,起始索引为当前索引加 1
backtrack(i + 1)
# 回溯:从 path 中移除元素
path.pop()
backtrack(0)
return result
方法二:迭代法
class Solution:
def subsets(self, nums: List[int]) -> List[List[int]]:
result = [[]]
for num in nums:
# 将当前元素添加到现有的所有子集中,形成新的子集
new_subsets = []
for subset in result:
new_subsets.append(subset + [num])
# 将新的子集添加到结果中
result.extend(new_subsets)
return result
测试用例
测试用例 1:
输入:nums = [1,2,3]
输出:[[],[1],[2],[1,2],[3],[1,3],[2,3],[1,2,3]]
测试用例 2:
输入:nums = [0]
输出:[[],[0]]
测试用例 3:
输入:nums = []
输出:[[]]
总结
本题是回溯算法的经典问题,主要考察对回溯思想的理解和应用。两种方法各有特点:
- 回溯算法:通过递归的方式生成所有子集,逻辑清晰易懂,空间复杂度较低。
- 迭代法:通过迭代的方式逐步构建所有子集,代码简洁,易于理解。
在实际应用中,两种方法都可以使用。对于初学者来说,回溯算法可能更容易理解,因为它的逻辑更直观。而迭代法则更简洁,不需要递归调用。
无论使用哪种方法,核心思想都是生成所有可能的子集,这对于解决许多组合问题都有帮助,例如组合总和、排列问题等。

1050

被折叠的 条评论
为什么被折叠?



