[Leetcode] Subsets

11 篇文章 0 订阅
10 篇文章 0 订阅

题目链接在此


Given a set of distinct integers, nums, return all possible subsets.

Note:

  • Elements in a subset must be in non-descending order.
  • The solution set must not contain duplicate subsets.

For example,
If nums = [1,2,3], a solution is:

[
  [3],
  [1],
  [2],
  [1,2,3],
  [1,3],
  [2,3],
  [1,2],
  []
]

列出一个集合的所有子集。


很多人知道这样一个规律:一个含有n个元素的集合,他的子集(包括它自己本身)的总数为2^n.

可是你有没想过这个规律背后的道理呢?


我们可以从这个角度解释这个规律:

比如一个集合S={10,20,30}.可以通过保留一些元素,去除一些元素来构造它的子集:

例如“留10去20留30”可以得到{10,30}这个子集,“去10去20去30”可以得到{}这个子集,等等。

也就是说,如果我们用1表示“保留”(或者“有”)、用0表示“去除”(或者“无”),那么每一个子集都可以用一条只含有1和0、长度为|S|的序列来表示。

例如 000表示{},001表示{30},010表示{20},011表示{20,30},100表示{10},101表示{10,30},110表示{10,20},111表示{10,20,30}。

集合中的每一个元素都可以对应为0或1两种情况,组合起来生成子集,那么含有n个元素的集合,自然就有2^n个子集了!


回到这道题来,我们就可以利用这个思路,通过构建出所有的序列,来得出所有的子集。

怎样得到序列呢?明眼人立刻能看出来,上面的那个例子里,000~111这8个序列其实就是0~7的二进制形式。那我们就通过二进制数来构造呗!


以下是我自己的代码,为了简洁,跳跃性比较强。

class Solution {
public:
	vector<vector<int> > subsets(vector<int>& nums) {
		  //  最终结果
		vector< vector<int> > v;

		  //  先要把原先的nums从小到大排序,因为要求"Elements in a subset must be in non-descending order"
		std::sort(nums.begin(), nums.end());

		  //  求出子集总数
		int total = pow(2.0, nums.size());

		  //  从0到2^n-1,每个数都转换成二进制,从而构造一个序列
		for(int i = 0; i < total; i++) {
			makeSeq(i, nums, v);
		}

		return v;
	}

private:
	  //  构造一个序列,对应一个子集
	void makeSeq(int x, vector<int>& nums, vector< vector<int> >& v) {
		vector<int> subset;

		  //  对于含有n个元素的集合,每个序列长度为n
		  //  为代码简洁起见,这里并没有把序列这一中间变量给实际的得出来,
		  //  只是进行了获取二进制的过程(不断模2+除2,进行n次:确保每个二进制长度恰好为n)
		  //  如果得出的二进制位是1,则代表保留对应位的元素,否则不保留。从而获得一个子集。
		for(int i = 0; i < nums.size(); i++) {
			if(x % 2) {
				subset.push_back(nums[i]);
			}
			x /= 2;
		}

		  // 将得到的子集放入v
		v.push_back(subset);
	}
};


当然还有一种比较笨拙但朴实的方法来获取序列:通过构造一颗高度为n(设根节点高度为0)的满二叉树来获取,他的叶子节点就代表一种序列。

例如n为3, 则根节点(第0层)设为000。

第1层的两个节点,修改父节点的第1位(分别修改成0和1),得到000和100

第2层,000的两个子节点,修改父节点的第2位(分别修改成0和1),得到000和010;

              100的两个子节点,修改父节点的第2位(分别修改成0和1),得到100和110

第3层,000的两个子节点,修改父节点的第3位(分别修改成0和1),得到000和001;

              010的两个子节点,修改父节点的第3位(分别修改成0和1),得到010和011;

              100的两个子节点,修改父节点的第3位(分别修改成0和1),得到100和101;

              110的两个子节点,修改父节点的第3位(分别修改成0和1),得到110和111


此时已经到第3层(叶子层)了,那么这一层的所有节点就是所有序列:

000,001,010,011,100,101,110,111

class Solution {
public:
	vector<vector<int> > subsets(vector<int>& nums) {
		vector< vector<int> > v;

		sort(nums.begin(), nums.end());

		  //  初始序列为n个0
		string start = "";
		for(int i = 1; i <= nums.size(); i++) {
			start += '0';
		}

		makeTree(start, 0, nums, v);
		return v;
	}

private:
	  //  seq是当前序列,level是层数(同时也表示当前序列要修改的第几位)
	void makeTree(string seq, int level, vector<int>& nums, vector< vector<int> >& v) {
		  //  层数为n,则到了叶子节点,序列已生成
		if (level == nums.size()) {
			vector<int> subset;

			  //  遍历序列,如果当前位是1,则代表保留对应位的元素,否则不保留。从而获得一个子集。 
			for(int i = 0; i < nums.size() ; i++) {
				if(seq[i] == '1')
					subset.push_back(nums[i]);
			}

			v.push_back(subset);
			return;
		}

		  //  将序列中带修改的那一位,修改为0,然后递归
		string tmp0 = seq;
		tmp0[level] = '0';
		makeTree(tmp0, level + 1, nums, v);

		  //  将序列中带修改的那一位,修改为1,然后递归
		string tmp1 = seq;
		tmp1[level] = '1';
		makeTree(tmp1, level + 1, nums, v);
	}
};

类似的思路在我做过的这道题里也用过。


下面是第三种方法,纯递归。

大体思路:从空集开始,每次复制当前所有的子集,在这些新复制的集合中加入新元素,直至无新元素可加。

其实这样的思路,递不递归都差不多。


class Solution {
public:
	vector<vector<int> > subsets(vector<int>& nums) {
		vector<vector<int> > v;

		std::sort(nums.begin(), nums.end());
	        
	 	addNewElem(v, nums, int(nums.size())-1);

	 	return v;
	}

private:
	void addNewElem(vector<vector<int> >& v, vector<int>& nums, int index) {
		if(index == -1)  {
			v.push_back(vector<int>());
			return;
		}
		        
		addNewElem(v, nums, index-1);

		int total = v.size();  //  此处为何要用一个新变量记录v的size请自行思考.找了我半天bug
		for(int i = 0; i < total; i++)  {
			vector<int> tmp(v[i]);
			tmp.push_back(nums[index]);
			v.push_back(tmp);
		}
	}
};


  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值