题目:260. 只出现一次的数字 III
给定一个整数数组 nums,其中恰好有两个元素只出现一次,其余所有元素均出现两次。 找出只出现一次的那两个元素。你可以按 任意顺序 返回答案。
进阶:你的算法应该具有线性时间复杂度。你能否仅使用常数空间复杂度来实现?
题目链接:260.只出现一次的数字 III
这道题如果我们不考虑时间复杂度来做题当然是非常的简单,这里我们就不做讨论了;今天我们要在时间复杂度为O(N)(不考虑空间复杂度)的条件下做这道题,其实还是有一定难度的。
在做题之前首先我们来回顾一下C语言学到的知识 & (按位与)和 ^(按位异或):
&:两个数按位与,两个数的相同二进制位中有0为0,都为1才为1,例如(这里方便观察我们只写了8位)
^:而两个数按位异或,则是二进制位相同为0,相异为1,例如:
这里如果我们再将这个得到结果按位异或6,会得到什么?
结果是5,这不是巧合,开始相当于我们是将0异或了5,0异或任何数会得到任何数,然后再异或了6,最后我们又异或了一个6,那我们异或了两个6,根据异或相同为0,那我们是不是就把原来的6给异或掉了,就只剩下5了。(大家也可以自己试一试其他的用例)
结论:异或相当于不进位加法,它可以储存不重复的数字 (1+1=0,1+0=1)。
如果我们从1异或到10,就相当于将这10个数都存起来了,再异或1到10,又会得到0。
知道了这个结论以后,做这道题,大家是不是就有思路了。
**1.**首先我们创建一个变量 ret 初始化为0,然后将它异或原数组中所有的数,这样按照我们刚才的结论,是不是就可以把数组中所有相同的数字给去掉了,ret 中就只剩下两个不相同的数字了。
int* singleNumber(int* nums, int numsSize, int* returnSize){
int ret = 0;
int i = 0;
for(i = 0; i < numsSize; ++i)
{
ret ^= nums[i];
}
}
**2.**既然得到这两个数字,能否得出答案呢?当然不会这么简单,这里我们只是得到了这两个数字相异或的二进制位,接下来我们就思考应该如何将二者分离开?
我们发现两数异或的结果为1的那一位,两个数必不相同,一个数为1,另一个就必为0,因为异或嘛,相异才能为1,所以我们如果找到异或结果2任意一个为1的那一位,然后根据这一位去分离数组中数,将这一位为0的分在一组,为1的分在另一组,而因为两个相同元素肯定会在同一组,异或时就会异或没了,最后一组只剩下不相同的那一个数,这样就可将两个数分开了。
首先需要找到,异或结果中哪一位为1
int* singleNumber(int* nums, int numsSize, int* returnSize){
int ret = 0;
int i = 0;
for(i = 0; i < numsSize; ++i)
{
ret ^= nums[i];
}
//寻找异或结果中为1的那一位的位置
int m = 0;
while( m < 32)
{
if(ret & (1<<m))//将1左移m位和ret相与,为1则条件成立,循环退出
break;
else
++m;
}
}
然后就可以将原数组分为两组,这里该怎么储存他们呢?用数组?可我们不知道大小,前面知道了异或,这里我们也可以用异或的方法。
int* singleNumber(int* nums, int numsSize, int* returnSize){
int ret = 0;
int i = 0;
for(i = 0; i < numsSize; ++i)
{
ret ^= nums[i];
}
int m = 0;
while( m < 32)
{
if(ret & (1<<m))
break;
else
++m;
}
int x1 = 0,x2 = 0;//创建变量x1,x2用来保存两个数
for(i = 0; i < numsSize; ++i)
{
if(nums[i] & (1<<m))
{
x1 ^= nums[i];//如果这一位是1则异或到x1上面去
}
else
{
x2 ^= nums[i];//否则异或到x2上面去
}
}
}
3. 最后则根据题目要求创建数组,赋值即可
int* singleNumber(int* nums, int numsSize, int* returnSize){
int ret = 0;
int i = 0;
for(i = 0; i < numsSize; ++i)
{
ret ^= nums[i];
}
int m = 0;
while( m < 32)
{
if(ret & (1<<m))
break;
else
++m;
}
int x1 = 0,x2 = 0;//创建变量x1,x2用来保存两个数
for(i = 0; i < numsSize; ++i)
{
if(nums[i] & (1<<m))
{
x1 ^= nums[i];//如果这一位是1则异或到x1上面去
}
else
{
x2 ^= nums[i];//否则异或到x2上面去
}
}
int* retArr = (int*)malloc(sizeof(int)*2);//根据题目要求我们需要创建动态数组,这里不需要释放
retArr[0] = x1;
retArr[1] = x2;
*returnSize = 2;//题目要求我们需要将*returnSize置为2,其实这里很多余。
return retArr;//最后返回数组
}
总结:这道题的技巧性很强,刚学完C语言第一次做很难把他做出来,特别是后面将两个数分离出来,在学C语言的时候可能都没有注意到 ^ 和 & 这些用法,所以学习编程还是要多做题,多总结,大家一起加油!!
大家学废了吗?有问题的地方欢迎大家在评论区留言指正哦。
互粉互赞~~