Recursion 求所有子集 @CareerCup

这道题可以用递归和迭代两种方法来做。


先说一下有收获的地方:

在递归中,更经常的采用的是头递归,即调用先递归处理子问题,然后基于子问题的解再处理当前问题!并且这样还能减少stack中保存的内容。

当然如果是比较简单的递归,即当前问题不依赖于子问题的解,如前面的那道findMagicIndex问题,就可以用尾递归。


还有一点是,

因为我在递归中ret值是不断往下传递的,所以如果不注意就可能造成concurrency exception。因此要预先计算好ret的size保存在一个变量中,而不能在for中每次去查询ret的size,因为随着不断增加入元素,size每次就都会变!


下面说一下两种思路,http://hawstein.com/posts/8.3.html 写的很好,直接搬过来。。


递归思路:

这道题目为什么可以用递归? 因为我们能找到比原问题规模小却同质的问题。比如我要求{1, 2, 3}的所有子集, 我把元素1拿出来,然后去求{2, 3}的所有子集,{2, 3}的子集同时也是{1, 2, 3} 的子集,然后我们把{2, 3}的所有子集都加上元素1后,又得到同样数量的子集, 它们也是{1, 2, 3}的子集。这样一来,我们就可以通过求{2, 3}的所有子集来求 {1, 2, 3}的所有子集了。而同理,{2, 3}也可以如法炮制。


迭代思路:

对于一个集合,它的子集一共有2n 个(包括空集和它本身)。它的任何一个子集, 我们都可以理解为这个集合本身的每个元素是否出现而形成的一个序列。比如说, 对于集合{1, 2, 3},空集表示一个元素都没出现,对应{0, 0, 0}; 子集{1, 3},表示元素2没出现(用0表示),1,3出现了(用1表示),所以它对应 {1, 0, 1}。这样一来,我们发现,{1, 2, 3}的所有子集可以用二进制数000到111 的8个数来指示。泛化一下,如果一个集合有n个元素,那么它可以用0到2n -1 总共2n 个数的二进制形式来指示。每次我们只需要检查某个二进制数的哪一位为1, 就把对应的元素加入到这个子集就OK。


package Recursion;

import java.util.ArrayList;

/**
 *  Write a method that returns all subsets of a set.
 *  
 *  写一个函数返回一个集合中的所有子集。
 *
 */
public class S9_4 {

	public static void main(String[] args) {
		ArrayList<Integer> list = new ArrayList<Integer>();
		for (int i = 0; i < 3; i++) {
			list.add(i);
		}
		ArrayList<ArrayList<Integer>> subsets = getSubsets2(list, 0);
		System.out.println(subsets.toString());
		
		ArrayList<ArrayList<Integer>> subsets2 = getSubsetIter(list);
		System.out.println(subsets2.toString());		
	}

	public static ArrayList<ArrayList<Integer>> getSubsets(ArrayList<Integer> list, int start) {
		ArrayList<ArrayList<Integer>> ret = new ArrayList<ArrayList<Integer>>();
		rec(list, start, ret);
		return ret;
	}
	
	// 递归方法,把第一个元素加到子问题返回的每一个子集中
	public static void rec(ArrayList<Integer> list, int start, ArrayList<ArrayList<Integer>> ret){
		if(start == list.size()){
			ret.add(new ArrayList<Integer>());
			return;
		}
		
		rec(list, start+1,ret);			// 头递归!必须是基于子问题的答案来处理当前问题!
		int head = list.get(start);		
		int size = ret.size();				// 注意不能用foreach来做,也不能直接把ret.size()放在for循环中,否则每次都改变ret的长度
												// 会得到ConcurrentModificationException或者直接耗尽内存
		for (int i=0; i<size; i++) {
			ArrayList<Integer> nal = new ArrayList<Integer>(ret.get(i));
			nal.add(head);
			ret.add(nal);
		}
	}
	
	// 利用二进制的表示来做,0表示不取,1表示取
	public static ArrayList<ArrayList<Integer>> getSubsetIter(ArrayList<Integer> list) {
		int len = list.size();
		ArrayList<ArrayList<Integer>> ret = new ArrayList<ArrayList<Integer>>();
		for(int i=0; i<Math.pow(2, len); i++){		// 子集的总数是2^len个
			int tmp = i;
			ArrayList<Integer> al = new ArrayList<Integer>();
			int index = 0;
			while(tmp != 0){
				if((tmp & 1) != 0){			// 如果最后一位是1,则取
					al.add(list.get(index));
				}
				tmp >>= 1;						// 不断右移直到为0
				index++;
			}
			ret.add(al);
		}
		return ret;
	}
	
	
	
	public static ArrayList<ArrayList<Integer>> getSubsets2(ArrayList<Integer> list, int start) {
		ArrayList<ArrayList<Integer>> ret = new ArrayList<ArrayList<Integer>>();
		rec2(new ArrayList<Integer>(), new ArrayList<Integer>(list), ret);
		return ret;
	}

	// 每个元素都有取或者不取两种状态,因此对两种状态分别递归
	/*
	 * (,abc):
	 * 
	 * (a,bc)
	 * 		(ab,c)
	 * 			(abc,)
	 * 			(ab,)
	 * 		(a,c)
	 * 			(ac,)
	 * 			(a,)
	 * 
	 * (,bc)
	 * 		(b,c)
	 * 			(bc,)
	 * 			(b,)
	 * 		(,c)
	 * 			(c,)
	 * 			(,)
	 * 
	 */
	public static void rec2(ArrayList<Integer> done, ArrayList<Integer> rest, ArrayList<ArrayList<Integer>> ret){
		if(rest.size() == 0){
			ret.add(new ArrayList<Integer>(done));		// 必须创建新数组保存状态!
			return;
		}
		
		Integer val = rest.get(0);		// 取得头元素		
		rest.remove(0);					// 更新rest
		
		done.add(val);					// 一种是取的状态
		rec2(done, rest, ret);
		done.remove(done.size()-1);		// 要记得恢复现场
		
		rec2(done, rest, ret);		// 另一种是不取的状态

		rest.add(0, val);				// 要记住恢复现场!
	}
}





评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值