题目: 在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。
示例:
示例 1:
输入:nums = [3,4,3,3]
输出:4
示例 2:
输入:nums = [9,1,7,9,7,9,7]
输出:1
这个题目和【力扣】136. 只出现一次的数字,面试题56 - I. 数组中数字出现的次数是一类题目,基本解法有:哈希表,排序,快慢指针,位运算。位运算是考察的重点,想要时间复杂度和空间复杂度达到最优,就需要进行位运算,那我们下面来看看这道题的三种解法,主要学习位运算的。
一、哈希表,时间O(N),空间S(N)
哈希表,就是[key,value],key保存数字,value保存出现次数,用hash.first可以访问key,hash.second访问second:
- 所以我们只需要一次遍历,将key,value值填入hash。
- 判断hash.second判断数字出现次数,等于1,我们输出key即可。
//1.哈希表 O(N)S(N)
int singleNumber1(vector<int>& nums)
{
if(nums.size()==0)
return 0;
unordered_map<int,int>hash;
for(int i=0;i<nums.size();i++)
{
hash[nums[i]]++;
}
for(auto i:hash)
{
if(i.second==1)//找到只出现了一次的
return i.first;
}
return 0;
}
二、排序,O(NlogN),S(1)
这个思路也很简单:
- 将数组排序,将nums[i]和nums[i+2]比较,不一样,则返回nums[i]。
- i<nums.size()-1,i+=3进行循环即可。
//2.排序
int singleNumber2(vector<int>& nums)
{
if(nums.size()==0)
return 0;
sort(nums.begin(), nums.end());
int i=0;
for(;i<nums.size()-2;i+=3)
{
if(nums[i]!=nums[i+2])
{
return nums[i];
}
}
return nums[i];//单个元素在结尾
}
三、位运算,O(N),S(1)
有一说一,这个位运算真的很厉害,只要是代码题没有计算机底层原理表示不出来的。它的思路就是,有一个数字出现了3次,那么它的32位表示的二进制之和,1的次数绝对也出现了3次。那么根据这个原理,思路就出来了:
- 数字用32位二进制存储,我们用b[32]数组,来保存所有数字加起来,32位上1出现的次数,即b[0]表示:所有数字二进制相加,0号位1出现的次数;简单来说就是如果有数字1,2,那么统计1【01】,2【10】相加后,32位数组上1出现的位置,相加为11,即b[0]=1,b[1]=1;
- 统计1出现的次数,flag=1,用nums[i]&flag则可以判断第一位,将flag<<=1,flag=01,判断第二位,这样循环32次,可以判断nums[i]数字的32位数上1出现的次数。
- 那么我们需要遍历数组nums,遍历b[32],那么时间复杂度为O(N*32)=O(N).
- 统计完成后,b[32]存储了数组全部元素和,1出现的次数,那么我们现在将它%3,如果某个元素出现次数为3次,那么1的次数%3后为0,剩下的1就是出现一次。
- 我们将b[32]中剩下的1,从高位循环累加转换为十进制即可,,我们定义res=0,但如何利用res和b[32]的值得出答案,我们在计算机组成原理中学过,<<可以帮我们进行二进制数值的变化,如01(1),左移10(2),所以现在我们只需要讨论是先<<还是先+,我们可以举例判断:如果有b[i]=0134,对3取余后为0101,那么有:【x】表示当前数值,32位太长,我们以8位举例,前面都补充0即可。
先加再移位 | 先移位再加 |
---|---|
加 0:【0000,0000】;移位:【0000,0000】 | 移位:【0000,0000】;加0:【0000,0000】 |
加1:【0000,0001】;移位:【0000,0010】 | 移位:【0000,0000】;加1:【0000,0001】 |
加0:【0000,0010】;移位:【0000,0100】 | 移位:【0000,0010】;加0:【0000,0010】 |
加1:【0000,0101】;移位:【0000,1010】 | 移位:【0000,0100】;加1:【0000,0101】 |
对比两种办法的结果,我们发现只有先移位,再加,才可以得到最后的结果,res才可以保存b[i]中二进制代表的数值。
那我们举个例子,nums = [3,4,3,3] ,答案为4;深刻理解一下这两步:给b[32]赋值;将b[32]%3后的值转为整数。由于这个示例的最大值为4,所以我们只画出b[32]的前8位即可。
那代码如下:
int singleNumber(vector<int>& nums)
{
if(nums.size()==0)
return 0;
int b[32]={0};//存储所有数字每位2进制出现1的次数
//遍历所有可能
for(int i=0;i<nums.size();i++)
{
int flag=1;
//unsigned int flag=1;力扣只支持无符号
for(int j=0;j<32;j++)//32位
{
if(nums[i]&flag)//判断每一位是否为1
{
b[j]+=1;//保存出现1的次数
}
flag<<=1;//判断下一位
}
}
//将b[32]中的二进制,转换为十进制
int res=0;//保存最后的值
for(int i=31;i>=0;i--)//从高到低位如0134,从0开始,0101
{
res<<=1;//先向左移动,
//先加再移动 0,00,01,010,011,0110
//先移动再加 00,01,010,0100,0101,所以选第二种
res+=(b[i]%3);//数值出现3次,那么它的对应位二进制也是3次,所以%3,剩下的就是只出现一个的
}
return res;
}
int main()
{
vector<int> nums;
nums.push_back(3);
nums.push_back(4);
nums.push_back(3);
nums.push_back(3);
cout<<singleNumber(nums);
}
加油哦!六一快乐呀,🍭。