78. 子集
题目来源
题目分析
这道题目要求我们生成一个不包含重复元素的整数数组 nums
的所有子集(幂集)。也就是说,我们需要找出从空集到包含所有元素的所有可能子集。题目说明解集中不能包含重复的子集。
题目难度
- 难度:中等
题目标签
- 标签:数组、回溯
题目限制
1 <= nums.length <= 10
-10 <= nums[i] <= 10
解题思路
为了解决这个问题,我们可以使用回溯算法,来构建所有可能的子集。我们可以从两个角度进行回溯:
- 从输入角度:对于每个元素,都有选择和不选择两种选择,这会产生两个分支,最终叶子节点就是答案。
- 从答案角度:首先枚举第一个元素选什么,然后依次枚举每个元素的选择,每个节点是n个分支,每个节点都是答案。
核心算法步骤
-
输入角度的回溯:
- 对于每个元素
nums[index]
,我们可以选择不将其包含在当前子集中,或者选择将其包含在当前子集中。 - 递归调用处理下一个元素,并根据选择将当前路径加入结果集中。
- 对于每个元素
-
答案角度的回溯:
- 每当我们递归至某个节点时,将当前路径添加到结果集中。
- 然后,依次枚举下一个可能的元素并继续递归。因为选择相同的元素是重复的,所以直接规定选择的元素下标要递增。
代码实现
以下是实现子集生成的Java代码:
/**
* 78. 子集
* @param nums 输入的整数数组
* @return ans 所有可能的子集
*/
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> ans = new ArrayList<>();
List<Integer> path = new ArrayList<>();
if (nums.length == 0) {
return ans;
}
backtrace(nums, 0, path, ans);
return ans;
}
// 输入角度的回溯
public void backtrace(int[] nums, int index, List<Integer> path, List<List<Integer>> ans) {
if (index == nums.length) {
ans.add(new ArrayList<>(path));
return;
}
// 不选num[index]
backtrace(nums, index + 1, path, ans);
// 选num[index]
path.add(nums[index]);
backtrace(nums, index + 1, path, ans);
// 恢复现场
path.remove(path.size() - 1);
}
// 答案角度的回溯
public void backtrace2(int[] nums, int index, List<Integer> path, List<List<Integer>> ans) {
ans.add(new ArrayList<>(path));
for (int i = index; i < nums.length; i++) {
path.add(nums[i]);
backtrace2(nums, i + 1, path, ans);
path.remove(path.size() - 1);
}
}
代码解读
-
输入角度的回溯:
- 当
index
达到数组长度时,递归结束,将当前路径加入结果集。 - 对于每个
nums[index]
,我们分别进行“选择”和“不选择”的操作,然后继续递归。
- 当
-
答案角度的回溯:
- 在每次递归时,直接将当前路径添加到结果集中。
- 然后,依次选择下一个元素,递归构建子集。
性能分析
-
时间复杂度:
O(2^n)
,其中n
是数组的长度。每个元素有选或不选两种选择,因此会生成2^n
个子集。 -
空间复杂度:
O(n)
,递归调用栈的深度为n
。
测试用例
你可以使用以下测试用例来验证代码的正确性:
int[] nums = {1, 2, 3};
List<List<Integer>> result = subsets(nums);
System.out.println(result);
// 输出: [[], [1], [2], [1,2], [3], [1,3], [2,3], [1,2,3]]
扩展讨论
优化写法
可以通过提前判断减少不必要的递归调用,进一步优化代码的性能。
其他实现
除此之外,我们也可以使用迭代法逐步生成子集,虽然逻辑较为复杂,但在某些场景下可能更高效。
总结
这道题目通过回溯算法帮助我们理解了在树形结构中如何通过选择路径来构建所有可能的子集。通过从输入和答案两个角度出发,我们能够有效地解决问题并生成所有子集。