Subsets I 问题描述:
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
个。
1、位操作。时间复杂度:O(n*2^n)
对于集合中的每一元素,只有两种可能:要么选取,要么不选取,因此形成了不同的子集和。这种思想和二进制的比特位一致,故可以用位操作来求解。
以集合s={1 , 2 , 3 }为例,子集和数目为2^3。
元素 子集中可能的结果
1 -> 选(1) or 不选(0) = 2
2 -> 选(1) or 不选(0) = 2
3 -> 选(1) or 不选(0) = 2
可以用一个下标0和1表示是否选择该数字,那么每一组3个0和1的组合表示一种选择,3位共有8种选择。因此:
子集数 = 2*2*2 = 2^3 = { { } , {1} , {2} , {3} , {1,2} ,
{1,3} , {2,3} , {1,2,3} }
按比特位的形式:从低位到高位。如1th个比特位代表是否选取1.
1th bit to 1 , 2th bit to 2 , 3th bit to 3
用二进制的0,1分布情况来表示是否选取某个元素
3th 2th 1th
j>>2 j>>1 j>>0
0) 0 0 0 -> 不选3, 不选2, 不选1 = { }
1) 0 0 1 -> 不选3, 不选2, 选1 = {1}
2) 0 1 0 -> 不选3, 选2, 不选1 = {2}
3) 0 1 1 -> 不选3, 选2, 选1 = {1, 2}
4) 1 0 0 -> 选3, 不选2, 不选1 = {3}
5) 1 0 1 -> 选3, 不选2, 选1 = {1, 3}
6) 1 1 0 -> 选3, 选2, 不选1 = {2, 3}
7) 1 1 1 -> 选3, 选2, 选1 = {1, 2, 3}
对于s的第1个元素s[0]=1,依次考察j=0~7,如果其二进制表示的第1位是1(j>>0 & 1==1),则选择s[0]加入第j+1个子集和,否则不选。
对于s的第2个元素s[1]=2,依次考察j=0~7,如果其二进制表示的第2位是1(j>>0 & 1==1),则选择s[1]加入第j+1个子集和,否则不选。
对于s的第3个元素s[2]=3,依次考察j=0~7,如果其二进制表示的第3位是1(j>>0 & 1==1),则选择s[2]加入第j+1个子集和,否则不选。
代码如下:
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
int n=nums.size();//数组元素个数
int nres=pow(2, n);//形成的组合数
vector<vector<int>> res(nres, vector<int> ());
sort(nums.begin(), nums.end());//(1)数组排序(要求非递增)
//(2)0~nres-1正好是n位2进制可以组成的数
for(int i=0;i<n;i++)
{//(3)对于每一个比特位(共n个)i
for(int j=0;j<nres;j++)
{//(4)对于0~nres中每一个数j
if((j>>i) & 1)
{//(5)依次考察j对应的第i位:1则选取,0则不选取
res[j].push_back(nums[i]);
}
}
}
return res;
}
};
2、递归
思想:可以当做遍历一颗二叉树,向左则选取,向右则不选取。每当该分支已遍历完n个数,则将该子集合加入到结果数组。深搜的思想,时间复杂度O(2^n),空间复杂度O(n)。
#include <iostream>
#include <vector>
#include<algorithm>
using namespace std;
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
int n=nums.size();
if(n==0) return res;
vector<int> sub;
sort(nums.begin(), nums.end());//(1)数组排序
subsearch(nums, sub, 0);//(2)深搜
return res;
}
private:
vector<vector<int>> res;
void subsearch(vector<int>& nums, vector<int> sub, int step)
{
if(step == nums.size())
{//(1)每当该分支已遍历完n个数,则将该子集合加入到结果数组并返回
res.push_back(sub);
return;
}
//(2)不选取nums[step]
subsearch(nums, sub, step+1);
//(3)选取nums[step]
sub.push_back(nums[step]);//step为0~n-1
subsearch(nums, sub, step+1);
//sub.pop_back();//可加可不加!!!!
}
};
int main()
{
Solution s;
vector<int> num = {3,2,1};
vector<vector<int> > result = s.subsets(num);
for(int i = 0;i < result.size();++i){
for(int j = 0;j < result[i].size();++j){
cout<<result[i][j]<<" ";
}
cout<<endl;
}
return 0;
}
3、回溯。
利用回溯法+深搜,依次生成指定元素数目(0~n)的子集和。
#include <iostream>
#include <vector>
#include<algorithm>
using namespace std;
class Solution {
public:
vector<vector<int>> subsets(vector<int>& nums) {
int n=nums.size();
if(n==0) return res;
vector<int> sub;
sort(nums.begin(), nums.end());//(1)数组排序
res.push_back(sub);//(2)加入空集
for(int nsub=1;nsub<=n;nsub++)
{//(3)加入其它子集,nsub表示当前生成的是包含多少个元素的子集
subsearch(nums, sub, nsub, 0);
}
return res;
}
private:
vector<vector<int>> res;
void subsearch(vector<int>& nums, vector<int> sub, int nsub, int index)
{
if(nsub == sub.size())
{//(1)当前子集元素个数已达到nsub则加入
res.push_back(sub);
return;
}
for(int i=index;i<nums.size();i++)
{//(2)从下标为index的元素往后遍历
sub.push_back(nums[i]);//(3)遍历到的元素加入子集合
subsearch(nums, sub, nsub, i+1);//(4)深搜
sub.pop_back();//(5)回溯
}
}
};
int main()
{
Solution s;
vector<int> num = {3,2,1};
vector<vector<int> > result = s.subsets(num);
for(int i = 0;i < result.size();++i){
for(int j = 0;j < result[i].size();++j){
cout<<result[i][j]<<" ";
}
cout<<endl;
}
return 0;
}
Subsets II 问题描述:
Given a collection of integers that might contain duplicates, 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,2]
, a solution is:
[
[2],
[1],
[1,2,2],
[2,2],
[1,2],
[]
]
所给序列有重复元素,如何求其子集合数目。
问题求解:
参照上题第三种方法,回溯,只是在遍历的时候加入判断语句即可
if(i>index && nums[i]==nums[i-1]) continue;
代码如下:
class Solution {
public:
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
int n=nums.size();
if(n==0) return res;
vector<int> sub;
sort(nums.begin(), nums.end());//(1)数组排序
res.push_back(sub);//(2)加入空集
for(int nsub=1;nsub<=n;nsub++)
{//(3)加入其它子集,nsub表示当前生成的是包含多少个元素的子集
subsearch(nums, sub, nsub, 0);
}
return res;
}
private:
vector<vector<int>> res;
void subsearch(vector<int>& nums, vector<int> sub, int nsub, int index)
{
if(nsub == sub.size())
{//(1)当前子集元素个数已达到nsub则加入
res.push_back(sub);
return;
}
for(int i=index;i<nums.size();i++)
{//(2)从下标为index的元素往后遍历
if(i>index && nums[i]==nums[i-1]) continue;//(3)在此处加入是否重复的判断!!!!
sub.push_back(nums[i]);//(4)遍历到的元素加入子集合
subsearch(nums, sub, nsub, i+1);//(5)深搜
sub.pop_back();//(6)回溯
}
}
};
方法二:与方法一稍有不同,但是是回溯法的基本应用。
class Solution {
public:
vector<vector<int>> subsetsWithDup(vector<int>& nums) {
int n = nums.size();
if(n==0) return res;
vector<int> sub;
sort(nums.begin(), nums.end());
subsearch(nums, sub, 0);
return res;
}
private:
vector<vector<int>> res;
void subsearch(vector<int>& nums, vector<int>& sub, int pos)
{
res.push_back(sub);//(1)
for(int i = pos; i < nums.size(); i++)
{
if(i > pos && nums[i] == nums[i-1]) continue;//(2)
sub.push_back(nums[i]);//(3)
subsearch(nums, sub, i + 1);//(4)
sub.pop_back();//(5)
}
}
};