一、问题
给定有n个不重复元素的集合P,打印出其所有子集。
二、思路
还是使用最有效最简单的方式:简化和特例。假设集合为{1,2,3,4},仍然把所有的子集分为4组:
a. 含有1的所有子集
1
1 2
1 2 3
1 2 3 4
1 2 4
1 3
1 3 4
1 4
b. 不含1但是有2的所有子集
2
2 3
2 3 4
2 4
c. 不含1,2但是有3的所有子集
3
3 4
d. 不含1,2,3但是含有4的子集
4
我们是如何不重复不遗漏地列出了所有的子集的呢?我们的做法是:对于集合中每一个数字i,列出包含i和比i位置靠后的元素的子集S(i)。而列出包含i和比i位置靠后的元素的子集又是怎样实现的呢?从我们列的方式可以看出,S(i)=i+S(i+1)(即先放入i,再放入S(i+1)的每个子集,构成的集合就是S(i))
可以看出与7.2中的问题很类似,也可以通过递归的方式来解决,不同点在于:
a. 不是排列,所以没有顺序的要求,{1,2}和{2,1}是重复的子集
b. 结果集的长短为0~n,而不像7.2中全是n
三、代码
1. 伪代码
public class Subset {
private static Integer[] input = { 1, 2, 3, 4 };
// Used for subset
private static Integer[] outputArr = new Integer[4];
private static void subset(int outIndex, int inIndex) {
// 打印当前子集
for (int i = 0; i < outIndex; i++) {
if (null != outputArr[i]) {
System.out.print(outputArr[i]);
System.out.print(" ");
}
}
System.out.println();
// 确定输入集合的取值开始范围
int index = outIndex > 0 ? inIndex : 0;
// 对于集合中每一个数字input[i],列出包含input[i]和比i位置靠后的元素的子集S(i)
for (int i = index; i < input.length; i++) {
//S(i)=i+S(i+1)(即先放入i,再放入S(i+1)的每个子集,构成的集合就是S(i))
outputArr[outIndex] = input[i]; // 放入第i个元素
subset(outIndex + 1, i + 1); // 生成从第i+1个元素开始的子集
}
}
public static void main(String[] args) {
subset(0, 0);
}
}
四、总结
四个部分中,最重要的一定是第二部分。在这一部分中,我的想法是一步步记录下解决问题过程中思路,而不是像很多书籍上那样直接告诉读者应该采用的方法。因为绝大部分人既非经验丰富,也非天资过人。正常人一定是不断地思考,不断地尝试,不断地纠正自己,才能找到最终的答案。在这个过程当中,有三类问题的出现是很常见的:
1. 出现错误,例如没有考虑到特殊情况,没有搞清楚约束条件等等。
2. 没有错误,但是方法不够好,或者是速度太慢,或者是空间消耗太大。
3. 完全没有思路。或者不知道题目的意思,或者知道如何在人脑中解决,但是无法变成计算机能实现的代码,或者完全不知如何下手。
与天冷就加衣,肚子饿就吃饭这些立竿见影的解决方法不同,解决这三类问题的方法要复杂的多,它更像武功修炼的过程。
1. 先练挨打。必须先体会这三类问题,首先是亲身去遭遇这些问题,然后达到能分辨这些问题的类型,并能找出类似的问题。
2. 学习招式。必须总结出不同的问题的类型,知道不同类型的问题的对策是什么,理解这样做的本质。
3. 夏练三伏,冬练三九。不断地练习,不断地重复过程:分析问题,联想类似的问题,确定问题类型,尝试对应的对策,修正对策,解决问题。
4. 无招胜有招。不断地规范化自己的思考过程,找出其中的Best Practise,最终形成成功模式,才能保证举一反三,乃至于升华和创新。最终把3中的过程变成了头脑的自然反应,秒杀问题。