#include<bits/stdc++.h>
using namespace std;
vector<int> cur;
vector<vector<int>> ans;
vector<vector<int>> fun(vector<int> &nums)
{
int n=nums.size();
for(int i=0;i<(1<<n);i++) // 遍历所有可能的子集
{
cur.clear();
for(int j=0;j<n;j++) // 对于每一位,判断是否包含在当前子集中
{
if(i&(1<<j))
{
cur.push_back(nums[j]);
}
}
ans.push_back(cur); // 将当前子集添加到答案中
}
return ans;
}
int main()
{
vector<int> nums;
int n;
cin>>n;
while(n--)
{
int q;
cin>>q;
nums.push_back(q);
if(cin.get()=='\n') break;
}
vector<vector<int>> ans1;
ans1=fun(nums);
for(auto e:ans1)
{
for(auto w:e)
{
cout<<w;
}
cout<<endl;
}
return 0;
}
给出数组长度:4
数组为:nums=[1,2,3,4]
相应的输出为:
上面给出的是位运算得出幂集的方法,详细逻辑思路以及位运算的一些用法如下:
所以,最终生成的子集(按照
i
的遍历顺序)是:
[ [], [3], [2], [2, 3], [1], [1, 3], [1, 2], [1, 2, 3]
左移(
<<
)(不好记的话,可以看尖角指向的是左就是左移)左移操作符(
<<
)用于将一个数的所有位向左移动指定的位数。例如,假设我们有一个二进制数0010
(十进制中的2),对它左移1位得到0100
(十进制中的4)。每向左移动一位,基本上相当于乘以2。因此,1 << n
实际上是生成了值2^n
。左移操作(
<<
)相当于乘以2的幂次方。右移(
>>
)(这里右移则尖角指向右方)右移操作符(
>>
)与左移相反,它将一个数的所有位向右移动指定的位数。右移通常被用于除以2的幂次方。例如,8 >> 1
将得到4
,因为8的二进制形式1000
向右移一位变成了0100
(即十进制中的4)。右移操作(
>>
)相当于除以2的幂次方(对于无符号整数)。- 代码通过位操作和掩码技术高效地遍历了所有子集的可能组合。
- 代码中使用了左移来生成所有可能的子集。核心思想是利用一个整数的每一位代表原集合中的一个元素,该位是否为1表示该元素是否被选中。
- 可能说的笼统了些,下面我们就用这个代码来看看过程
以输入
nums = [1, 2, 3]
为例,解释该代码如何生成所有子集。首先,
nums
数组的长度n
为3,所以1 << n
等于1 << 3
等于8
。( i为外层循环 ———>for(int i=0;i<(1<<n);i++)中的 i 。这个循环从0迭代到
2^n - 1
(因为1 << n
生成了2^n
,我们需要的是从0到2^n - 1
的所有数)每一个i
代表了一种子集的可能性。 )这意味着会有
2^n = 2^3 = 8
个子集,包括空集和自身。遍历的i
值从0
到7
(即二进制的000
到111
),每一个i
都代表了一个可能的子集。接下来,我们逐个分析每个
i
值如何产生对应的子集:- i = 0 (二进制 000): 没有位被设置,因此没有元素被选中。子集是
[]
。- i = 1 (二进制 001): 只有最低位被设置,表示只选取
nums
中的第三个元素(索引从0开始)。子集是[3]
。- i= 2 (二进制 010): 第二位被设置,表示选取
nums
中的第二个元素。子集是[2]
。- i = 3 (二进制 011): 第一和第二位被设置,表示选取
nums
中的第二个和第三个元素。子集是[2, 3]
。- i = 4 (二进制 100): 第三位被设置,表示选取
nums
中的第一个元素。子集是[1]
。- i = 5 (二进制 101): 第一和第三位被设置,表示选取
nums
中的第一个和第三个元素。子集是[1, 3]
。- i = 6 (二进制 110): 第二和第三位被设置,表示选取
nums
中的第一个和第二个元素。子集是[1, 2]
。- i = 7 (二进制 111): 所有位都被设置,表示选取
nums
中的所有元素。子集是[1, 2, 3]
。
再次对核心代码解释
for(int j=0;j<n;j++)
{
if(i&(1<<j))
{
cur.push_back(nums[j]);
}
}
这里,
i
变量是一个整数,它通过位运算来表示当前遍历的子集。在二进制表示中,i
的每个位对应nums
数组中的一个元素。如果i
的某一位是1
,则表示相应位置的元素应该被选中加入到当前的子集cur
中;如果是0
,则表示相应的元素不被选中。
for (int j = 0; j < n; j++)
: 这个循环遍历nums
数组的每个元素,n
是nums
数组的长度。
1 << j
: 这个表达式将数字1
向左移j位。移位操作实际上是在创建一个只在第j位为1
的二进制数,用于检查i
的第j位是否被设置(即是否为1
)。例如,如果j为2,则1 << 2
的结果是100
(二进制),即十进制的4
。
i & (1 << j)
: 这个表达式执行按位与操作。它检查i
的第j位是否为1
。如果i
的第j位为1
,那么这个表达式的结果为非零值(真),表示nums
数组中对应的元素应该被选中。如果i
的第j位为0
,结果为0
(假),表示不选择该元素。
cur.push_back(nums[j])
: 如果上述条件满足(即i
的第j位为1
),则将nums
数组中的第j个元素添加到当前子集t
中。通过这个循环,我们能够根据
i
的每个位是否为1
来决定是否选择nums
数组中对应位置的元素。这样,通过从0
到2^n - 1
变化i
值的过程,我们能遍历并生成nums
数组的所有可能子集。
下面再提供相应的递归算法,如果你有兴趣,可以根据下面算法进行代码实现。
vector<vector<int>> sub(vector<int>& nums,int n) { if(n==0) return {{}}; auto temp=sub(nums,n-1); auto ans=temp; for(auto num:temp) { num.push_back(nums[n-1]); ans.push_back(num); } return ans; } vector<vector<int>> subsets(vector<int>& nums) { int n=nums.size(); return sub(nums,n); } }
快去写一次吧,相信大侠会获得新知识。