1. 题目

https://leetcode.cn/problems/subsets/
考察点:
2. 解法
解法有三种
Leetcode 78是一个关于子集合的问题,给定一个不重复的整数数组nums,返回所有可能的子集(幂集)。
有多种方法可以用Java实现,比如:
- 使用迭代法,每次遍历数组中的一个元素,将其加入到已有的子集中,并生成新的子集。
- 使用回溯法,每次选择或不选择数组中的一个元素,递归地构建子集,并将结果添加到列表中。
- 使用位运算法,每个子集可以用一个二进制数表示,其中第i位为1表示选择数组中的第i个元素,为0表示不选择。遍历所有可能的二进制数,将对应的子集添加到列表中
解法一:使用迭代法
思路
方法一的思路是利用数学上的集合运算,如果一个集合有n个元素,那么它的子集有2^n个,可以用二进制数表示。例如,如果集合是{1,2,3},那么它的子集有8个,分别是:
- 000: 空集
- 001: {1}
- 010: {2}
- 011: {1,2}
- 100: {3}
- 101: {1,3}
- 110: {2,3}
- 111: {1,2,3}
可以看到,每次从左到右遍历一个二进制位,就相当于选择或不选择当前元素。所以,我们可以用一个循环来遍历数组中的每个元素,每次将其加入到已有的子集中,并生成新的子集。例如,当遍历到第一个元素1时,我们将其加入到空集中,得到{1},然后将空集和{1}都添加到结果列表中。当遍历到第二个元素2时,我们将其加入到空集和{1}中,得到{2}和{1,2},然后将这两个新的子集也添加到结果列表中。以此类推,直到遍历完所有元素,就得到了所有的子集。
代码逻辑
具体实现
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
if (nums == null) {
return result;
}
result.add(new ArrayList<>()); // 空集
for (int num : nums) { // 遍历数组中的每个元素
List<List<Integer>> temp = new ArrayList<>(); // 创建一个临时列表存储新生成的子集
for (List<Integer> list : result) { // 遍历已有的子集
List<Integer> newList = new ArrayList<>(list); // 复制一份
newList.add(num); // 加入当前元素
temp.add(newList); // 添加到临时列表中
}
result.addAll(temp); // 将临时列表中的所有子集添加到结果列表中
}
return result;
}
解法二:使用回溯法
思路
使用回溯算法的思路是,
从空集开始,每次考虑数组中的一个元素,是否加入到当前的子集中,然后递归地处理剩余的元素。
当遍历完所有元素后,将当前的子集加入到解集中。
为了避免重复,可以先对数组进行排序,然后保证每次只从当前元素之后的元素中选择。
代码逻辑
代码的逻辑是这样的:
- 首先,对数组进行排序,方便去重。
- 然后,创建一个解集res,用来存放所有的子集,以及一个临时子集subset,用来存放当前的子集。
- 接着,调用一个回溯函数backtrack,传入数组nums,起始位置start,临时子集subset和解集res。
- 在回溯函数中,首先将当前的子集subset复制一份并加入到解集res中。
- 然后,从起始位置start开始遍历数组nums中的元素,对于每个元素nums[i],有两种选择:加入到当前子集中或不加入。
- 如果选择加入到当前子集中,就将nums[i]添加到subset的末尾,然后递归地调用回溯函数,传入数组nums,下一个起始位置i+1,临时子集subset和解集res。
- 如果选择不加入到当前子集中,就跳过这个元素,继续遍历下一个元素。
- 在递归返回后,需要回溯,即将刚刚添加到subset的末尾的元素移除,恢复到之前的状态。
- 这样,就可以遍历所有可能的子集,并将它们加入到解集中。
具体实现
class Solution {
public List<List<Integer>> subsets(int[] nums) {
// 排序数组,方便去重
Arrays.sort(nums);
// 创建解集和临时子集
List<List<Integer>> res = new ArrayList<>();
List<Integer> subset = new ArrayList<>();
// 调用回溯函数
backtrack(nums, 0, subset, res);
// 返回解集
return res;
}
// 回溯函数
private void backtrack(int[] nums, int start, List<Integer> subset, List<List<Integer>> res) {
// 将当前子集加入到解集中
res.add(new ArrayList<>(subset));
// 遍历数组中的元素
for (int i = start; i < nums.length; i++) {
// 将当前元素加入到子集中
subset.add(nums[i]);
// 递归地处理剩余的元素,注意下一次的起始位置是i+1
backtrack(nums, i + 1, subset, res);
// 回溯,将当前元素从子集中移除
subset.remove(subset.size() - 1);
}
}
}
解法三:使用位运算法
思路
代码逻辑
具体实现
public List<List<Integer>> subsets(int[] nums) {
List<List<Integer>> result = new ArrayList<>();
int n = nums.length; // 数组长度
int m = 1 << n; // 子集个数,等于2的n次方
for (int i = 0; i < m; i++) { // 遍历所有可能的二进制数
List<Integer> list = new ArrayList<>(); // 创建一个空列表存储当前子集
for (int j = 0; j < n; j++) { // 遍历数组中的每个元素
if ((i & (1 << j)) != 0) { // 如果二进制数的第j位为1,表示选择该元素
list.add(nums[j]); // 将该元素添加到列表中
}
}
result.add(list); // 将当前列表添加到结果列表中
}
return result;
}
浙公网安备 33010602011771号