问题1:只出现一次的数字
解法1:哈希表/排序/集合方法
使用哈希表来记录每个元素出现次数,进而筛选出只出现一次的元素
遍历数组,若元素首次出现,则加入集合,再次出现则删除该元素,最后集合中剩下的元素为只出现一次的元素。
排序后,对数组遍历,若该位置元素与下一元素相同,则跳过该元素。若不想同则表明其为只出现一次的元素。
class Solution:
def singleNumber(self, nums: List[int]) -> int:
nums = list(sorted(nums))
i = 0
while(i<len(nums)-1):
if nums[i] != nums[i+1]:
return nums[i]
else:
i += 1
while (nums[i] == nums[i-1]):
i+=1
return nums[-1]
( 上述方法的空间复杂度都为O(n),下面尝试方法对空间复杂度进行优化 )
解法2:位运算
该题可使用异或运算,同时异或运算具有以下性质:
因此对于该数据,共2m+1个元素[a,c,a,b,b],m为出现次数为2的元素数量。基于上述异或运算性质3,数组中所有元素之间的异或可表示为如下形式:
根据上述性质2和性质1,上式和化简成如下形式:
因此,数组中的全部元素的异或运算结果即为数组中只出现一次的数字。
class Solution:
def singleNumber(self, nums: List[int]) -> int:
return reduce(lambda x, y: x ^ y, nums)
问题2:只出现一次的数字2
解法1:和上一题类似
解法2:依次确定每一个二进制位
跟上题不同的是,多次出现元素的出现次数为3,因此异或的性质不适用,例a
⨁
\bigoplus
⨁a
⨁
\bigoplus
⨁a=a。
因此需要寻求其它解决方法,通过观察发现,对于数组中3m+1个元素,除去只出现一次的元素,剩下的元素在每一位上的二进制数之和都为3的倍数(0或3),因此对于任一二进制位,所有元素在该位和除以3的余数就为只出现一次元素在该位的元素值。(0+a % 3 = a,3+a % 3 = a)
需要注意的是,如果使用的语言对「有符号整数类型」和「无符号整数类型」没有区分,那么可能会得到错误的答案。这是因为「有符号整数类型」(即int类型)的第 31个二进制位(即最高位)是补码意义下的符号位,对应着 − 2 31 -2^{31} −231 ,而「无符号整数类型」由于没有符号,第 31个二进制位对应着 2 31 2^{31} 231 。因此在某些语言(例如 python)中需要对最高位进行特殊判断。
class Solution:
def singleNumber(self, nums: List[int]) -> int:
ans = 0
for i in range(32):
total = sum((num >> i) & 1 for num in nums)
if total % 3:
# Python 这里对于最高位需要特殊判断
if i == 31:
ans -= (1 << i)
else:
ans |= (1 << i)
return ans
问题3:只出现一次的数字3
解法:位运算
类似于问题1的解法,但是不同的是该题中数组有2m+2个元素,其中有两个不同的只出现一次的元素。
同样我们采用位运算来求解,对于数组中所有元素求异或结果中任一二进制位,出现两次的2m个元素的总体异或值为0,最终该位结果由剩下的两个不同的元素决定,若在该二进制位值不同则为1,相同则为0。
因此,我们可以根据结果中为1的二进制位对两个元素进行区分,进而对那2m个元素进行划分,例:元素在该二进制位为1的属于A组,为0的属于B组。完成划分后,该问题就等价于在A、B的两个等价于问题1的子问题,由此可以分别在A、B中求出两个不同的number_1和number_2。
那么如何求出这个结果中值为1的二进制位呢?
可以使用位运算 x & -x 取出 x 的二进制表示中最低位那个 1。原理可以参考该链接。
https://blog.csdn.net/oyoung_2012/article/details/79932394
class Solution:
def singleNumber(self, nums: List[int]) -> List[int]:
xorsum = 0
for num in nums:
xorsum ^= num
lsb = xorsum & (-xorsum)
type1 = type2 = 0
for num in nums:
if num & lsb:
type1 ^= num
else:
type2 ^= num
return [type1, type2]