Cracking the coding interview--Q8.3

本文介绍了一种生成集合子集的方法,并通过二进制表示和递归实现来展示不同子集的生成过程。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原文:

Write a method that returns all subsets of a set.

译文:

写一个函数返回一个集合中的所有子集。


对于一个集合,它的子集一共有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。代码如下:


	/**
	 * @param str
	 * @return
	 * 
	 * 对于一个集合,它的子集一共有2n 个(包括空集和它本身)
	 * 计算所有的子集,如果元素包含n个,则可以使用n位二进制表示,
	 * 
	 * 比如说, 对于集合{1, 2, 3},空集表示一个元素都没出现,对应{0, 0, 0}; 子集{1, 3},表示元素2没出现(用0表示),
	 * 1,3出现了(用1表示),所以它对应 {1, 0, 1}。这样一来,我们发现,{1, 2, 3}的所有子集可以用二进制数000到111 的8个数来指示。
	 * 使用链表记录2n 个结果,其中每个结果都是一个链表记录当前二进制的选择情况
	 * 
	 */
	public static LinkedList<LinkedList<Integer>> getAllSubSet(String []str) {
		LinkedList<LinkedList<Integer>> link = new LinkedList<LinkedList<Integer>>();
		int len = str.length;
		int sum = 1;
		for(int i=0; i< len; i++) {
			sum <<= 1;
		}
		System.out.println("总共子集有 sum = " + sum);
		
		for(int i=0; i<sum; i++) {
			LinkedList<Integer> subList = new LinkedList<Integer>();
			int count = len;
			int sub = i;
			while(count > 0) {
				if((sub & 1) == 1) {
					subList.add(1);
				} else {
					subList.add(0);
				}
				count --;
				sub >>= 1;			
			}
			link.add(subList);
		}
		return link;
	}


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


/**
	 * @param len
	 * @param curIndex
	 * @return
	 * 
	 * 使用递归的方式
	 * 
	 */
	public static LinkedList<LinkedList<Integer>> getAllSubSetByReCursion(int len, int curIndex) {
		 LinkedList<LinkedList<Integer>> subSets = new LinkedList<LinkedList<Integer>>();
		 if(curIndex == len) {
			 LinkedList<Integer> part = new LinkedList<Integer>();
			 subSets.add(part);
		 } else {
			 LinkedList<LinkedList<Integer>> part = getAllSubSetByReCursion(len, curIndex + 1);
			 
			 for(LinkedList<Integer> sub: part) {
				 subSets.add(sub);
				 LinkedList<Integer> newSub = new LinkedList<Integer>();
				 for(Integer temp: sub) {
					 newSub.add(temp);
				 }
				 newSub.add(1);
				 subSets.add(newSub);
			 }
		 }
		 return subSets; 
	}



总代码:

package chapter_8_Recursion;

import java.util.LinkedList;
import java.util.Scanner;

/**
 * 
 * 写一个函数返回一个集合中的所有子集。
 *
 */
public class Question_8_3 {
	
	/**
	 * @param str
	 * @return
	 * 
	 * 对于一个集合,它的子集一共有2n 个(包括空集和它本身)
	 * 计算所有的子集,如果元素包含n个,则可以使用n位二进制表示,
	 * 
	 * 比如说, 对于集合{1, 2, 3},空集表示一个元素都没出现,对应{0, 0, 0}; 子集{1, 3},表示元素2没出现(用0表示),
	 * 1,3出现了(用1表示),所以它对应 {1, 0, 1}。这样一来,我们发现,{1, 2, 3}的所有子集可以用二进制数000到111 的8个数来指示。
	 * 使用链表记录2n 个结果,其中每个结果都是一个链表记录当前二进制的选择情况
	 * 
	 */
	public static LinkedList<LinkedList<Integer>> getAllSubSet(String []str) {
		LinkedList<LinkedList<Integer>> link = new LinkedList<LinkedList<Integer>>();
		int len = str.length;
		int sum = 1;
		for(int i=0; i< len; i++) {
			sum <<= 1;
		}
		System.out.println("总共子集有 sum = " + sum);
		
		for(int i=0; i<sum; i++) {
			LinkedList<Integer> subList = new LinkedList<Integer>();
			int count = len;
			int sub = i;
			while(count > 0) {
				if((sub & 1) == 1) {
					subList.add(1);
				} else {
					subList.add(0);
				}
				count --;
				sub >>= 1;			
			}
			link.add(subList);
		}
		return link;
	}
	
	/**
	 * @param len
	 * @param curIndex
	 * @return
	 * 
	 * 使用递归的方式
	 * 
	 */
	public static LinkedList<LinkedList<Integer>> getAllSubSetByReCursion(int len, int curIndex) {
		 LinkedList<LinkedList<Integer>> subSets = new LinkedList<LinkedList<Integer>>();
		 if(curIndex == len) {
			 LinkedList<Integer> part = new LinkedList<Integer>();
			 subSets.add(part);
		 } else {
			 LinkedList<LinkedList<Integer>> part = getAllSubSetByReCursion(len, curIndex + 1);
			 
			 for(LinkedList<Integer> sub: part) {
				 subSets.add(sub);
				 LinkedList<Integer> newSub = new LinkedList<Integer>();
				 for(Integer temp: sub) {
					 newSub.add(temp);
				 }
				 newSub.add(1);
				 subSets.add(newSub);
			 }
		 }
		 return subSets; 
	}
	
	public static void printAllSubSet(LinkedList<LinkedList<Integer>> link, String []str) {
		for(LinkedList<Integer> temp: link) {
			int index = 0;
			for(Integer t: temp) {
				if(t == 1) {
					System.out.print(" " + str[index] + " ");
				} else {
					System.out.print(" 不获取 ");
				}
				index ++;
			}
			System.out.println();
		}
	}
	
	public static void main(String args[]) {
		String str[] = "hello world china".split(" ");
		LinkedList<LinkedList<Integer>> result = getAllSubSet(str);
		
		printAllSubSet(result, str);
		
		String str2[] = "a b c".split(" ");
		

		 LinkedList<LinkedList<Integer>> result2 = getAllSubSetByReCursion(str2.length, 0);
		 
		 printAllSubSet(result, str2);
	}
}



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值