前端刷题路-Day79:只出现一次的数字(题号136)

263 阅读3分钟

这是我参与8月更文挑战的第13天,活动详情查看:8月更文挑战

只出现一次的数字(题号136)

题目

给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。

说明:

你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?

示例 1:

输入: [2,2,1]
输出: 1

示例 2:

输入: [4,1,2,1,2]
输出: 4

链接

leetcode-cn.com/problems/si…

解释

这题啊,这题是经典简我击(简单题我重拳出击)。

但最后的结果十分尴尬,简我击失败了。

仔细看看说明,有两点:

  • 线性时间复杂度
  • 不使用额外空间

线性时间复杂度其实就是O(n),没什么不好理解的,不使用额外空间就是不能使用额外的变量,这也很好理解。

总的来说就是遍历一次拿到结果,并且不能使用额外的遍历来存储相关内容。

由于这个限制,显然不能使用正常的hash,使用Map来记住数字出现的情况, 如果第二次出现就是去掉Map上的这个属性,最后遍历完整个数组,Map上剩余的唯一属性就是最后的结果了。

代码写起来也很简单👇:

var singleNumber = function(nums) {
  const map = new Map()
  for (const num of nums) {
    if (!map.has(num)) {
      map.set(num, true)
    } else {
      map.delete(num)
    }
  }
  return [...map][0][0]
};

由于这不符合官方的说明,就不放在答案里了,这里看一眼得了。

笔者能想到的“线性”时间复杂度的方法就是从数组开头开始遍历,每次获取第一个元素在第一个位置后的indexOf,如果该数字的indexOf不为-1,那就证明这个数字存在两次,然后去掉数组开头和indexOf的位置的数据,更新数组。

这个操作也就是去掉两个重复的数字,如此直到遇到indexOf-1的数字,那这个数字就是出现一次的数字的,如果干到最后数组就剩一个元素了,那剩余的最后一个数字也就是出现一次的数字了。

这种做法唯一的问题就是indexOf是否符合线性时间复杂度的规范,如果不管这个,那么该方法确实是符合题目要求的。

自己的答案(indexOf

逻辑说完了,👇看看代码:

var singleNumber = function(nums) {
  while (nums.length > 1) {
    const secondIndex = nums.indexOf(nums[0], 1)
    if (secondIndex < 0) return nums[0]
    nums.splice(secondIndex, 1)
    nums.shift()
  }
  return nums[0]
};

逻辑还是比较简单的,就是不知道secondIndex算不算额外的空间,其实也可以去掉这个变量名称,跟下面的代码糅合在一起,但理解起来可能不是很顺畅,于是还是这样写吧。

更好的方法(异或)

说实话,异或这个方法笔者是真的想不到,还记得异或的三个特性么?

  1. 一个数和 0 做 XOR 运算等于本身:a ⊕ 0 = a
  2. 一个数和其本身做 XOR 运算等于 0:a ⊕ a = 0
  3. XOR 运算满足交换律和结合律:a ⊕ b ⊕ a = (a ⊕ a) ⊕ b = 0 ⊕ b = b

这里其实可以推导一下,我们将所有的数字利用异或运算符链接起来,可以是一个这样的式子:

4 ^ 1 ^ 2 ^ 1 ^ 2

根据交换律和结合律,这个式子可以变成这样:

2 ^ 2 ^ 1 ^ 1 ^ 4

再根据上面的第二条定理:

一个数和其本身做 XOR 运算等于 0:a ⊕ a = 0

可以将相同的数字数字都替换成0,那么此时的式子是这样的:

0 ^ 0 ^ 4

再根据第2条定理进行转换,式子就变成了这样:

0 ^ 4

此时再应用第一条定理:

一个数和 0 做 XOR 运算等于本身:a ⊕ 0 = a

可以得到这个式子的结果其实就是4,也就是这个例子的答案。

关于0和0异或的操作不用纠结,因为不管是奇数0还是偶数0,根据第二条定理,最后都会变成一个0,到最后也就是会剩下0和出现一次的数字了。

所以最后的解法就是:

var singleNumber = function(nums) {
  return nums.reduce((a,b)=> a^b)
};

比较难想到,不用太纠结。



PS:想查看往期文章和题目可以点击下面的链接:

这里是按照日期分类的👇

前端刷题路-目录(日期分类)

经过有些朋友的提醒,感觉也应该按照题型分类
这里是按照题型分类的👇

前端刷题路-目录(题型分类)