题目描述:
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
输入: [2,2,1] 输出: 1
示例 2:
输入: [4,1,2,1,2] 输出: 4
解题思路:
这个题目第一眼看,嗯,很简单呢,跟上一题目好像产不多似的。然而题目的要求不一样这一次的题目要求使用线性时间复杂度,并且最好不使用额外的存储空间,就遍历操作至多执行一遍,并且不创建新的对象或者数组。
那么我首先想到了js数组的indexOf方法和lastIndexOf方法,可以获取数组元素第一次出现和最后一次出现时的索引,因此只需要判断两者的值是否相同即可。代码如下:
let length=nums.length;
for(let i=0;i<length;i++){
if(nums.indexOf(nums[i])==nums.lastIndexOf(nums[i])){
return nums[i];
}
}
return null;
但是我提交答案之后发现执行时间以外的非常长,大概400-500ms,这不像是一个数组的线性操作所需的事件,所以我们想到indexOf方法可能在内部的实现也是需要遍历操作的,实际上是js作者帮我们完成了第二层的遍历操作,那么我认为这个解法并不是一个好的解决方案,但是我想了很久也没有想出一个真正满意的解法。
于是无奈的查看的其他同学的解法,解法如下:
let count=0;
let a = 0;
for(let index = 0;index<nums.length;index++){
a =a^nums[index];
}
return a;
首先我懵逼了,这个^符号是什么运算符来着?,我想了5秒想起来这是逻辑运算符异或,异或运算符的意思就是,运算符两边的值是相同的(都为真或者都为假),那么运算结果为假(false、0),否则运算结果为真(true、1),如下:
true^true -->false
false^false-->false
true^false-->true
false^true-->true
//或者
1^1-->0
0^0-->0
1^0-->1
0^1-->1
即两个值相等的到false或者0,否则得到true或者1。
但是我们只了解这些仍然无法理解上面给出的算法,我们还需要理解一个操作叫做按位异或。
首先我们知道计算机中的所有数据在内存中最终都是以二进制进行存储的,二进制就是计算机能够理解解释的语言。那么把我们常用的数字转化为二进制是什么样的呢?举例如下:
十进制: 1 ,2,3,4,5,6,7,8
二进制 :1,10,11,100,101,110,111,1000
二进制即逢二进一的算术方法。
那么什么是按位异或呢,即比较两个二进制数字的每一位上的值,如
10^10=00;
11^11=00;
11^10=01;
11^01=10;
观察上面的运算我们发现,相同的二进制数字经过逻辑异或运算后得到的值都是0。
再有:
1^0^1=0
1^1^0=0
0^1^1=0
所以我们发现异或操作交换操作数的位置并不会影响运算结果。
那么我们既可以利用以上两点解决这个算法题目了。
首先,我们需要知道对数字进行异或操作实际上是对操作数对应的二进制数字进行逻辑异或运算,
其次,我们根据题意知道数组中只有一个不重复的元素,其余元素都出现两次,这个其他元素出现两次也是解题关键,正因此我们可以通过异或运算进行解题。
我们只需要假设数组的顺序为所有相同的元素都成对的排列在数组的前面,最后是我们想要的不重复的元素,那么对对每对相同数数字进行逻辑异或操作,那么最后计算得的值永远是0,而使用0对唯一不重复的元素执行异或操作仍然得到原来的值。
总结:
以上解法用到了以下几点知识点:
1、逻辑异或的意义
2、逻辑异或操作数的顺序不会影响运算结果(基本运算符的交换律)
3、任何数与0进行逻辑异或运算不会发生改变