这是力扣上一道简单的题目:
给你一个 非空 整数数组 nums ,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
你必须设计并实现线性时间复杂度的算法来解决此问题,且该算法只使用常量额外空间。
示例 1 :
输入:nums = [2,2,1]
输出:1
示例 2 :
输入:nums = [4,1,2,1,2]
输出:4
示例 3 :
输入:nums = [1]
输出:1
我第一时间想到的是用hashmap来算,分别统计各个数字出现的次数,然后找出那个只出现一次的。
var singleNumber = function(nums) {
const result = {};
let res;
nums.forEach(item=>{
if(result[item]){
result[item]+=1;
}else{
result[item] = 1;
}
})
Object.keys(result).forEach(key=>{
if(result[key] === 1) res = key;
})
return res;
};
这个算法的时间复杂度为O(n), 空间复杂度也为O(n)。但是题目的要求是只能使用常量的额外空间,也就是O(1)的复杂度。然后我就去问了GPT,有没有更快的算法(懒得看官方题解🙈)
GPT说可以用异或运算。
异或运算(^)是一种二进制运算,用于对两个二进制数的每一位进行比较,若相应位的值不同,则该位的结果为1,否则为0。利用异或运算,我们可以将整个数组中所有的元素进行异或运算,最终得到的结果即为只出现一次的元素。因为重复的元素异或之后会变成0,而只出现一次的元素与0异或的结果是它本身。
具体的实现方法如下:
var singleNumber = function(nums) {
let res = 0;
nums.forEach(item => res ^= item);
return res;
};
异或运算的本质,就是相同的位会变成0,不相同的位会变成1。这也就是为什么相同的数字做异或运算会相互抵消,只留下了唯一的那个。
当然,用这种解法的前提是重复的数字是偶数次出现,如果是奇数次出现就不行了。
那么,如果是奇数次出现的话,除了hashMap,还有其他的算法吗?
很遗憾,GPT说没有😂。
这是GPT给出的理由:
如果重复出现的数字次数是奇数次,排序算法就无法直接解决这个问题。因为排序后,重复的数字会相邻出现,无法判断哪个数字只出现了一次。此时,我们可以使用哈希表来解决这个问题
复习一下位运算
既然提到了异或运算,那就让GPT给我讲讲其他的位运算
-
与运算(&):对于两个二进制数的每一位进行比较,如果两个数的相应位都为1,则该位的结果为1,否则为0。例如,3 & 5 的结果是1,因为3的二进制表示是11,5的二进制表示是101,它们的相应位中只有最后一位是1,因此运算结果是1。
-
或运算(|):对于两个二进制数的每一位进行比较,如果两个数的相应位中至少有一个为1,则该位的结果为1,否则为0。例如,3 | 5 的结果是7,因为3的二进制表示是11,5的二进制表示是101,它们的相应位中有最后一位、第二位和第一位是1,因此运算结果是111,即7的二进制表示。
-
取反运算(~):对一个二进制数的每一位进行取反操作,即0变成1,1变成0。例如,~3 的结果是-4,因为3的二进制表示是11,取反之后变成00,然后加上符号位变成-100,即-4的二进制表示。
-
左移运算(<<):将一个二进制数的所有位向左移动指定的位数。例如,3 << 2 的结果是12,因为3的二进制表示是11,向左移动两位之后变成1100,即12的二进制表示。
-
右移运算(>>):将一个二进制数的所有位向右移动指定的位数。例如,12 >> 2 的结果是3,因为12的二进制表示是1100,向右移动两位之后变成11,即3的二进制表示。
这里可以用集合的概念来理解下:与运算就是位间取交集、或运算就是位间取并集,等等
总结:
这是一次和GPT一起学算法的经历,这之间GPT犯了很多次傻,不能太依赖😆,顺便复习了下位运算