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},然后将这两个新的子集也添加到结果列表中。以此类推,直到遍历完所有元素,就得到了所有的子集。
代码逻辑
具体实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | 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的末尾的元素移除,恢复到之前的状态。
- 这样,就可以遍历所有可能的子集,并将它们加入到解集中。
具体实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | 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 ); } } } |
解法三:使用位运算法
思路
代码逻辑
具体实现
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | 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; } |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 分享4款.NET开源、免费、实用的商城系统
· 全程不用写代码,我用AI程序员写了一个飞机大战
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· 白话解读 Dapr 1.15:你的「微服务管家」又秀新绝活了
· 上周热点回顾(2.24-3.2)
2019-04-29 Spring 框架用到的 9 个设计模式汇总!
2019-04-29 设计模式总结
2019-04-29 spring中的设计模式
2019-04-29 深入解析spring中用到的九种设计模式