前言
LeetCode上有几道题特别相似,分别是leetcode 136只出现一次的数(简单),137只出现一次的数Ⅱ(中等),260只出现一次的数Ⅲ。
这几道题当然有很多中解法,但是其中有一种解法特别独特,也是很多人无法想到的,那么就是利用位运算,说起位运算,其实很多人都忘记了,只是知道怎么回事,但是不知道它具体有什么用处。读完本文,你就能感觉到它的神奇之处了
复习
既然需要使用位运算,那么在这里我们就先复习一下吧
左移和右移
一:只出现一次的数字I
题目
这道题需要借助到异或运算符对于参与运算的两个对象,如果对应二进制位不同结果是1,否则就是0。主要使用的性质有**:0异或任何数等于任何数,任何数异或自己都等于0,异或运算满足结合律和交换律**
所以对于上面的数组【4,1,2,1,2】,我们可以4 ^ 1 ^ 2 ^ 1 ^ 2
,也就是4 ^ 1 ^ 1 ^ 2 ^ 2
,所以4 ^ (1 ^ 1) ^ (2 ^ 2)
,所以4 ^ 0 ^ 0,所以结果就是4
class Solution {
public:
int singleNumber(vector<int>& nums)
{
int value=0;
for(auto e : nums)
{
value=value^e;
}
return value;
}
};
二:只出现一次的数字II
题目
对于这道题,有一种方法是自动机,但是比较难以理解,详情可以看这篇文章的讲解
但我们最常使用的还是直接用位运算
首先需要发现规律,将我们的数的二进制位每一位对应相加,然后对每一位的和进行取余
这里大家注意观察最终取余的结果和目标值。之所以这样做的原因是因为,如果其他书都出现了3次,只有目标数出现了1次,那么每一位为1的个数无非就是3的倍数或3的倍数+1,而3的倍数+1也就是我们要找的那个情况
代码如下:
class Solution
{
public:
int singleNumber(vector<int>& nums)
{
int ret=0;
for(int i=0;i<32;i++)
{
int count=0;
for(int j=0;j<nums.size();j++)
{
if((nums[j]>>i & 1) == 1)
{
count++;
}
}
if(count%3!=0)
{
ret=ret | 1 << i;
}
}
return ret;
}
};
第一个for
循环用于控制数字的每一个二进制位,进入第一个for
循环之后,定义一个count
,用来保存此时各数字对应二进制位的和,然后第二个循环首先判断该数字的这一二进制位是否是1,判断时只需要让这个数字右移由i
控制的位数,让判断的位置跑到最低位,然后与1进行与运算,因为与运算是两者“同时是1才能是1”,所以一旦结果不等于1,说明判断的这一位就不是1,如果是1则count++,第二个for循环结束后,所有数字对应的二进制位加和就完毕了,接着根据之前的性质判断,如果取余不等于0,说明count是3的倍数+1,是由于那个单独出现的数字导致的,因此ret与1进行或运算,让其最后一位变为1(这个1就是单独出现的数字的某一二进制位),由于之前右移了i位,所以现在让其左移i位,回正
二:只出现一次的数字III
这道题和第一题有点相似,但是它是有两个单独出现的数字,如果直接使用异或,那么得到结果就是拿两个目标数的异或值,肯定是不行的
所以我们可以这样做,将元素分为两组,每组包含一个目标值,然后组内异或,那么每组的最终结果就是一个目标值
比如【a,b,a,b,c,d,e,f,e,f】进行分组后,组A为:【a,a,b,b,c】,组B为:【e,e,f,f,d】,组A得到c,组B得到d。
c和d不同,所以其异或的结果一定不是0,我们只研究其最右面那一位(其实任何一位都可以),也就是让其异或结果变为0或1,接着以0或1作为标准,让数组内的元素进行分组,比如说异或结果是1,我就这样判断:和1与运算的结果为1的分为1组,为0的分为另外一组,我不需要管它到底在哪一组,我只明白相同的数字肯定在一组
那么最后一点就是如何让其异或结果变为0或1呢,假设异或结果是
temp,只需要temp & (-temp)
,因为负数等原数按位取反+1
class Solution {
public:
vector<int> singleNumber(vector<int>& nums)
{
int temp=0;
for(auto e : nums)
temp ^=e;//保留两个不同的数的异或值
unsigned int div=temp & (-(unsigned int)temp);//注意这是32位环境,注意强转为无符号
vector<int> group(2.0);
for(auto e : nums)
{
if((e & div)==0)//组内异或
{
group[0] ^= e;
}
else
{
group[1] ^=e;
}
}
return group;
}
};