目录
[1. 子集](#1. 子集)
[1.1 题目解析](#1.1 题目解析)
[1.2 解法](#1.2 解法)
[1.3 代码实现](#1.3 代码实现)
[2. 找出所有子集的异或总和再求和](#2. 找出所有子集的异或总和再求和)
[2.1 题目解析](#2.1 题目解析)
[2.2 解法](#2.2 解法)
[2.3 代码实现](#2.3 代码实现)
1. 子集
https://leetcode.cn/problems/subsets/description/
给你一个整数数组 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
中的所有元素 互不相同
1.1 题目解析
题目本质
生成给定集合的幂集(所有子集)。对一个包含 n 个互不相同元素的集合,子集总数必然是 2^n。
常规解法
枚举"每个元素要/不要",把做出的选择拼成子集。
问题分析
朴素地"列举所有组合"如果没有系统性,会漏解或重复;而且需要一套机制来有序地遍历所有选择状态。复杂度下界就是输出规模 2^n量级,时间至少 O(2^n)。
思路转折
想系统地覆盖所有"要/不要"的选择树 → 用回溯 DFS,每层决定当前位置是否选入,天然避免重复;
1.2 解法
算法思想
回溯:从索引 k 开始,先将当前 path 加入 result;对 i = k 到 n-1,尝试加入 nums[i],递归到 i+1,再回退。
**i)**维护全局 result(答案)与 path(当前子集)。
**ii)**定义 dfs(nums, k):先把 path 的拷贝加入 result。
**iii)**从 i = k 到 n-1:
-
把 nums[i] 加入 path;
-
递归 dfs(nums, i+1);
-
回退:移除 path 尾元素。。
**iv)**入口调用 dfs(nums, 0),返回 result。
易错点
-
加入答案时必须拷贝当前路径(new ArrayList<>(path)),否则后续回退会改动已存结果。
-
回溯后记得弹栈(remove(path.size()-1)),否则路径会"越堆越长"。
-
题目无序要求,不必排序;若要字典序输出,可先 Arrays.sort(nums)。
1.3 代码实现
java
import java.util.*;
class Solution {
List<List<Integer>> result;
List<Integer> path;
public List<List<Integer>> subsets(int[] nums) {
result = new ArrayList<>();
path = new ArrayList<>();
dfs(nums, 0);
return result;
}
private void dfs(int[] nums, int k) {
// 1) 任何时刻的路径都是一个合法子集
result.add(new ArrayList<>(path));
// 2) 从当前位置开始,依次尝试选择一个元素加入
for (int i = k; i < nums.length; i++) {
path.add(nums[i]); // 选择
dfs(nums, i + 1); // 递归到下一个起点
path.remove(path.size() - 1);// 回退
}
}
}
复杂度分析
-
时间复杂度:O(n⋅2^n)
-
空间复杂度:O(n)
2. 找出所有子集的异或总和再求和
https://leetcode.cn/problems/sum-of-all-subset-xor-totals/description/
一个数组的异或总和 定义为数组中所有元素按位 XOR
的结果;如果数组为 空 ,则异或总和为 0
。
- 例如,数组
[2,5,6]
的 异或总和 为2 XOR 5 XOR 6 = 1
。
给你一个数组 nums
,请你求出 nums
中每个 子集 的 异或总和 ,计算并返回这些值相加之 和 。
注意: 在本题中,元素 相同 的不同子集应 多次 计数。
数组 a
是数组 b
的一个 子集 的前提条件是:从 b
删除几个(也可能不删除)元素能够得到 a
。
示例 1:
输入:nums = [1,3]
输出:6
解释:[1,3] 共有 4 个子集:
- 空子集的异或总和是 0 。
- [1] 的异或总和为 1 。
- [3] 的异或总和为 3 。
- [1,3] 的异或总和为 1 XOR 3 = 2 。
0 + 1 + 3 + 2 = 6
示例 2:
输入:nums = [5,1,6]
输出:28
解释:[5,1,6] 共有 8 个子集:
- 空子集的异或总和是 0 。
- [5] 的异或总和为 5 。
- [1] 的异或总和为 1 。
- [6] 的异或总和为 6 。
- [5,1] 的异或总和为 5 XOR 1 = 4 。
- [5,6] 的异或总和为 5 XOR 6 = 3 。
- [1,6] 的异或总和为 1 XOR 6 = 7 。
- [5,1,6] 的异或总和为 5 XOR 1 XOR 6 = 2 。
0 + 5 + 1 + 6 + 4 + 3 + 7 + 2 = 28
示例 3:
输入:nums = [3,4,5,6,7,8]
输出:480
解释:每个子集的全部异或总和值之和为 480 。
提示:
1 <= nums.length <= 12
1 <= nums[i] <= 20
2.1 题目解析
题目本质
计算"所有子集的按位异或值之和"。
常规解法
先生成全部子集,再逐个子集把元素异或起来累加到答案。
问题分析
子集数是2^n。暴力遍历并对每个子集做一次线性异或,时间下界 O(n⋅2^n)。若把子集都存到内存中(如 ret),还需要 O( O(n⋅2^n) 空间。
思路转折
用回溯生成所有子集,计算 XOR 并累加;
2.2 解法
算法思想
回溯:维护全局 ret(收集子集)与 path(当前路径)。dfs(nums, k) 先把 path 拷贝进 ret,再枚举 i=k..n-1:加入 nums[i] → 递归到 i+1 → 回退。最终遍历 ret,对子集元素做异或并累加。
**i)**准备全局容器:ret = new ArrayList<>(); path = new ArrayList<>();
**ii)**调用 dfs(nums, 0) 生成所有子集:
-
把 path 的拷贝放入 ret;
-
从 i = k 到 n-1:path.add(nums[i]) → dfs(nums, i+1) → path.remove(...) 回退。
**iii)**遍历 ret:对每个子集线性求异或 xor ^= x,并做 result += xor。
**iv)**返回 result。
易错点
- 加入答案时要拷贝 path:new ArrayList<>(path),避免回溯改动已存结果。
2.3 代码实现
java
import java.util.*;
class Solution {
List<List<Integer>> ret;
List<Integer> path;
public int subsetXORSum(int[] nums) {
int result = 0;
ret = new ArrayList<>();
path = new ArrayList<>();
// 可选:排序对异或结果无影响,通常无需
// Arrays.sort(nums);
dfs(nums, 0);
// 遍历每个子集,计算其异或值并累加
for (List<Integer> cur : ret) {
int xor = 0;
for (int x : cur) {
xor ^= x;
}
result += xor;
}
return result;
}
// 回溯生成所有子集:从索引 k 开始做选择
private void dfs(int[] nums, int k) {
// 当前路径就是一个合法子集
ret.add(new ArrayList<>(path));
// 从 k 开始依次选择一个元素加入
for (int i = k; i < nums.length; i++) {
path.add(nums[i]); // 选择
dfs(nums, i + 1); // 递归到下一个起点
path.remove(path.size() - 1); // 回退
}
}
}
复杂度分析
-
时间复杂度:生成子集 O(2^n)
-
空间复杂度:回溯栈与路径O(n),若计入 ret 存储子集,空间 O(n⋅2^n)