1. 问题描述:
请编写一个方法,返回某集合的所有非空子集。
给定一个int数组A和数组的大小int n,请返回A的所有非空子集。
保证A的元素个数小于等于20,且元素互异
2. 思路分析:
在纸上进行简单分解问题之后我们发现有一种技巧。(从数组中的第一个元素和空集开始考虑,而且每次可以保持这个集合也可以向集合中添加当前元素)
先从简单一点的例子来着手进分析,数组A中的元素分别是1,2,3
假如把数组中的每个元素分割来进行考虑的,可以分成{1},{2},{3},然后分别进行考虑,{1}可以保持这个集合,也可以添加2变成{1,2},即可以变成{1},{1,2},同理{2}可以变成{2},{2,3},{3}就不能够增加了,再对{1},{1,2}进行增加元素,可以变成:{1},{1,2},{1,3},{1,2,3}
分析是可以这样分析但是使用递归的代码来实现的话可能有点困难,因为这里涉及到的是三棵树(可以把数组中每个元素看成是一棵树)但是使用循环加上遍历的方法也是可以实现的
我们可以使用另外一方法来进行分析:一开始的时候我们可以将数组中的元素分成空集合第一个元素这里是{A[0]} 和 {}即{{1}}和{}
然后对{} 和{1}开始处理,我们可以保留{},也可以增加当前元素,可以变成{1},{1,2},{},{2},对于上面的结果我们可以使用同样的方法进行处理,保留原来这个集合或者向这个集合添加元素{1},{1,3},{1,2},{1,2,3},{2},{2,3},{3},{}
那么就可以得到所有的子集的结果,而上面的思路是可以递归的方法来进行实现的,因为只涉及到一棵树,而且我们数的每一层结果的处理都是相同的,每一次都可以对上一次返回的进行遍历再往集合中添加元素进行层层更新得到最终的答案进行返回
其中可以使用Set这个数据结构来存储元素,因为每一次都要往集合中添加集合,所以使用集合嵌套嵌套集合的方法进行处理
需要注意的问题是假如需要求解非空集合的话,不能在for循环中从Set中边遍历和边删除元素,因为这样是不允许的,会抛出ConcurrentModificationException异常
总结:分解问题的时候需要注意技巧,分析能不能使用递归来实现,假如可以需要注意的是递归每一次处理的方法都是相同的,
所以分解的问题必须是相同的,假如不同就不能够使用递归来处理,其次要注意递归解决的时候出口的问题,因为涉及到边界的问题,自己在写代码的时候可以边想边测试一下
3. 下面是具体的代码实现
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;
public class Main {
public static void main(String[] args) {
Scanner sc = new Scanner(System.in);
int n = sc.nextInt();
int A[] = new int[n];
for(int i = 0; i < n; i++){
A[i] = sc.nextInt();
}
Set<Set<Integer>> set = solve(A, n , n - 1);
for(Set<Integer> set1 : set){
if(set1.size() == 0){
//集合不可以一边遍历一边删除
set.remove(set1);
break;
}
}
System.out.println(set);
sc.close();
}
private static Set<Set<Integer>> solve(int[] A, int n, int cur) {
//cur最多为2
Set<Set<Integer>> newSet = new HashSet<>();
if(cur == 0){
Set<Integer> nil = new HashSet<>();
Set<Integer> first = new HashSet<>();
first.add(A[0]);
newSet.add(nil);
newSet.add(first);
return newSet;
}
Set<Set<Integer>> oldSet = solve(A, n, cur - 1);
for(Set<Integer> set : oldSet){
//不要加入到oldSet中因为正在遍历着oldSet
//保留原来的集合
newSet.add(set);
Set<Integer> clone = (Set<Integer>) ((HashSet) set).clone();
clone.add(A[cur]);
newSet.add(clone);
}
return newSet;
}
}