逆袭!GPT助你轻松揭秘数组中独特数字的寻找之道

49 阅读4分钟

这是力扣上一道简单的题目:

给你一个 非空 整数数组 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,则该位的结果为1,否则为0。例如,3 & 5 的结果是1,因为3的二进制表示是11,5的二进制表示是101,它们的相应位中只有最后一位是1,因此运算结果是1。

  2. 或运算(|):对于两个二进制数的每一位进行比较,如果两个数的相应位中至少有一个为1,则该位的结果为1,否则为0。例如,3 | 5 的结果是7,因为3的二进制表示是11,5的二进制表示是101,它们的相应位中有最后一位、第二位和第一位是1,因此运算结果是111,即7的二进制表示。

  3. 取反运算(~):对一个二进制数的每一位进行取反操作,即0变成1,1变成0。例如,~3 的结果是-4,因为3的二进制表示是11,取反之后变成00,然后加上符号位变成-100,即-4的二进制表示。

  4. 左移运算(<<):将一个二进制数的所有位向左移动指定的位数。例如,3 << 2 的结果是12,因为3的二进制表示是11,向左移动两位之后变成1100,即12的二进制表示。

  5. 右移运算(>>):将一个二进制数的所有位向右移动指定的位数。例如,12 >> 2 的结果是3,因为12的二进制表示是1100,向右移动两位之后变成11,即3的二进制表示。

这里可以用集合的概念来理解下:与运算就是位间取交集、或运算就是位间取并集,等等

总结:

这是一次和GPT一起学算法的经历,这之间GPT犯了很多次傻,不能太依赖😆,顺便复习了下位运算