题目重述
示例
法一:三重循环
最容易想到的方法应该是直接三重循环 这里有一个小技巧,就是并不用对i,j,k都进行0-length的搜索,而是让i<=j<=k;
之后根据重复次数在进行叠加即可,这种方法可以通过java的评测,但不能通过c++的评测
int count=0;
for(int i=0;i<nums.length;i++){
for(int j=i;j<nums.length;j++){
for(int k=j;k<nums.length;k++){
if((nums[i]&nums[j]&nums[k])==0){
if(i==j&&j==k){
count++;
}else if(i==j||j==k){ //不用写i==k, 因为i==k的时候其实是i,j,k全相等的
count+=3;
}else {
count +=6;
}
}
}
}
}
return count;
时间复杂度O(
);
空间复杂度O(1);
法二:加入hash优化算法
在思考完暴力解法后,我们开始尝试优化算法.
考虑到题目的要求是三元组的与&结果为0,而对于三元组很容易想到的一个优化是先做二元组,此时我们把二元组用hash存储以降低时间复杂度,注意到数据范围在 [0,),那么显然nums[i]&nums[j]也处于这个范围[0,
);
我们先使用二重循环枚举i,j并用一个长度为的vector记录出现的每一种nums[i]&nums[j]的出现次数,未出现的默认0次,之后我们再使用二重循环,其中一重为k的枚举,另一重为记录出现频数的vector数组的枚举.+=即可(默认为0)
代码如下:(注意==优先级大于&)
int count=0;
vector<int> cnt(1<<16,0);//初始化一个2^16的数组
for(int i=0;i<nums.size();i++)
for(int j=0;j<nums.size();j++)
cnt[nums[i]&nums[j]]++;
for(int k=0;k<nums.size();k++){
for(int x=0;x< (1<<16);x++){
if((nums[k]&x)==0)
//注意这里要有一个()
//对于表达式a&b==0,我们以为应该先判断a&b,再判断是否等于0,其实恰好相反
count+=cnt[x];
}
}
return count;
时间复杂度O(
+Cn);
空间复杂度O(C);
C=
也就是vector的size
法三:hash + 子集优化
子集优化的主要目标是减小法二中的C
子集优化的主要做法是不再以 if((nums[k]&x)==0) 去匹配 0~ 的每一个可能的(nums[i]&nums[j]),而是针对于nums[k]去统计每一个与它自身可以相与&为0的二元组nums[i]&nums[j]的总数.
这里我们把nums[k]与-1进行异或运算得到y,这样一来,满足要求的二元组的二进制表示中所包含的1就一定需要是y的子集.
举个例子,比如nums[k]是011000,(我们只考虑6位,其余同理),则y就是100111,那么与nums[k]相与&为0的就一共有16种,{每一种的1的位置} {y的1的位置} ,是它的子集.
那么我们怎么枚举子集呢,这里运用了二进制枚举子集的技巧:
记当前异或后的二进制数是y,我们要求y的所有 (1子集)
一:
首先令sub=y因为它本身也是自己的子集,
二:
接着让sub = (sub-1) & y;直到sub=0;
//简单理解:
sub从y走到0,每次-1而不是-2,因此可以走到所有可能位置,而与y进行与操作,又保证它一定是y的(1子集).
//深层理解:
以y=10100为例,二进制下减1->10011
即把最右的1变0,把该1后面的所有0变1
而这个时候再对y进行与操作,相当于总体上是
即把最右的1变0,把该1后面的所有y上面1的位置变1,其余保持0不变
相当于二进制的跨步减法. 10100->10000->00100->00000.
//还是无法理解的建议算一下这个例子
代码如下:
vector<int> cnt(1<<16,0);
for(int x:nums)
for(int y:nums)
cnt[x&y]++;
int count=0;
for(int x:nums){
// 找到所有与nums[k]相&得 0的二元组(不计顺序)个数
int y=x^0xffff; //得到x与2^16的 异或
// 找到此时y的所有1子集
for(int sub=y;sub!=0;sub=(sub-1)&y){
count+=cnt[sub];
}
count +=cnt[0];
}
return count;
时间复杂度O(
+Cn);
空间复杂度O(C);
对于C,当数据x随机时,x(nums[k])的二进制形式中1的个数期望为8个,二进制枚举1子集需要枚举
次, 当最坏情况下,x的二进制形式中1个数为16个,二进制枚举需要枚举
次.