目录
题目概述:
题目解读:
题目对时间效率和空间效率都有要求,但空间要求可视为空气,线性时间复杂度即为o(n)。
基本思路;
(1)暴力美学解法:
直接采用排序的方法,对nums数组进行由小到大,再创建一个0~n的完整的数组,再去和nums数字一一对比,就可以找到只出现一次的两个元素,在无限制的情况下不失为一种好方法,但题目要求为o(n),此方法就不合格,所以就不多bb了。
(2)奇淫巧计解法:
采用异或(^)解题,用z=0去异或nums数组的每一个数,此时得到两个解的异或值,要将其分离开,所以去找到二进制z的首位(从右向左)不为零的数(也可以是任何一个位,不影响结果),假设z的第3位为1,就把第3位是否为1为条件把nums数组分为两组,每组异或到一起,这样每组的结果就是所要求解的两个数,接下来,围绕此思路细细展开。
异或(^)原理:
小小的(^)符号,除了在一些计算题中,好像再也没有见过他的身影,但是在此类题目中却是“关键先生”,用0去异或一个数,数值不变(0^5=5),同一个数异或两次为0(5^5=0),而且不用连续异或相同的数才会抵消,可以不在意顺序。
全局代码:
int* singleNumber(int* nums, int numsSize, int* returnSize)
{
int z=0;
for(int i=0;i<numsSize;i++)
{
z^=nums[i];
}
long tmp=1;
int m=0;
while(1)
{
if(z&(tmp<<m))
break;
else
++m;
}
// while(!(z&tmp))
// {
// tmp<<1;
//
// }
int x=0,y=0;//用来保存两个解
for(int i=0;i<numsSize;++i)
{
if(nums[i]&(tmp<<m))
{
x^=nums[i];
}
else
y^=nums[i];
}
*returnSize=2;
int *p=(int*)malloc(sizeof(int)*2);
p[0]=x;
p[1]=y;
return p;
}
步骤分析:
以提供的案例一为例;
(1)
int z=0;
for(int i=0;i<numsSize;i++)
{
z^=nums[i];
}
定义一个整型的变量z,然后循环去异或数组的数值,得到z=3^5,然后去分离3和5。
(2)
long tmp=1;
int m=0;
while(1)
{
if(z&(tmp<<m))
break;
else
++m;
}
看到长整型,就有小伙伴要质疑博主是个笨比了,其实不然,有个非常恶心的测试案例,如果不设置为长整型的话,再下边的位运算会越界的。
通过一个循环找到了(3^5)首位为1的位置(其实可以是任意位置为1,不影响结果),然后以此为条件去分组。
(3)
int x=0,y=0;//用来保存两个解
for(int i=0;i<numsSize;++i)
{
if(nums[i]&(tmp<<m))
{
x^=nums[i];
}
else
y^=nums[i];
}
按照第m位为1和为0为条件,分为两组,相同的数会分到同一组然后异或相互抵消掉,酱紫,x和y就是求得的解。
(4)
*returnSize=2;
int *p=(int*)malloc(sizeof(int)*2);
p[0]=x;
p[1]=y;
return p;
这个题还有个小bug(苦笑),提供了一个多余的参数 int* returnSize,这是个用来返回解的数量的,题目已经说是两个了,还要这个参数,不是脱了裤子放屁唛?
看这里!
其实还有一个小性质,可以省略一些位运算,可运用到第(2)步
z=z&(-z);
z与-z进行与预算,可以直接得到(3^5)首位为一的位置(说位置不太准确,相当于得到将1位移到首位为1的地方的数值,
可以看到得到是相同的结果,这样无需定义长整型变量tmp,但要注意后方代码变动!
最后的最后:
这类题在力扣有个1,2,3个题目,此为第三题,还有一个较为简单的父类题,17.04消失的数字,用来加强练习。
做算法题,要多学习和总结方法,然后疯疯癫癫的去刷题升级,如果能写一篇文章能更好的掌握技巧。
勿喷,如果有错误或模棱两可的地方,请一定要不吝啬的指出,谢谢!