题目
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
输入: [2,2,1]
输出: 1
示例 2:
输入: [4,1,2,1,2]
输出: 4
解题思路
总觉得自己会了,但是优化算法想不出来……说明还是不怎么会啊
对于这道题,一个很常规的思路就是所有元素异或,然后剩下的元素就是所求的元素。但是为什么异或可以呢?
异或是把十进制数字转化为二进制,然后按位异或。异或保证了出现偶数次的位数都为0,出现奇数次的位数为1,所以对所有数字异或后,出现偶数次的数字,用异或后,每位都变为0,只有出现了1次的数字保留了下来。
那么,如果其他数字都出现了奇数次,应该怎么办呢?
实际上,用上面的思路也很容易想到了,假设其他数字都出现了k次,那么我们只要能用一种运算,保证每位上出现了k次的数字都变为0,就可以找到只出现1次的数字了。
把所有数字都转成k及以下的进制,然后对每一位求和,再对k取模,最终剩下的数字就是出现1次的数字。
不能用k以上的进制,因为这样没法区分该位的结果。比如,输入[5, 4, 1, 1, 5, 1, 5], k = 3
,如果直接用10进制,对个位数相加得到22
,取模后得到1
,不知道该位应该是1
还是4
还是7
为了最省空间,使用k进制最好
注意要对负数做额外处理,因为python中负数比较特殊,所以额外写了一个符号位
为什么符号和数字可以分开计算?
注意到,在计算的时候,可以把符号和数字分开算。这是因为我们计算的时候,实际上对每一位都是取模的,所以忽略了进位带来的改变。因此数字可以单独计算
为什么符号也可以直接相加?
对于符号来说,实际上只有正和负两种情况。用什么数字做代表都无所谓,关键在于取模相加。
以上思路可以推广到任意次数,即假设其他数字出现了k次,只有1个数字出现了非k次的情况。
代码
异或版:
class Solution:
def singleNumber(self, nums: List[int]) -> int:
ans = 0
for num in nums:
ans ^= num
return ans
k进制版:
class Solution:
def decimal_to_k(self, num: int, k: int) -> tuple:
"""
Change num to k-base, return digits in a string
Returns:
sign: int, 1 for positive, -1 for negative
res: str, the left is the least digit
"""
if num == 0:
return '0', 1
res = ''
sign = 1 if num > 0 else -1
num *= sign
while num > 0:
res += str(num % k)
num //= k
return res, sign
def basek_xor(self, num1: str, num2: str, k: int) -> int:
"""
Xor by mod k, from left to right, each digit is (num1[i] + num2[j]) % k, with no carry
"""
res = ''
for i in range(min(len(num1), len(num2))):
digit1, digit2 = int(num1[i]), int(num2[i])
res += str((digit1 + digit2) % k)
i += 1
if i < len(num1):
res += num1[i:]
if i < len(num2):
res += num2[i:]
return res
def k_to_decimal(self, num: str, sign: int, k: int) -> int:
"""
base-k to decimal
Args:
num: str, left -> right is least -> most
sign: either 1 (positive) or k - 1 (negative)
"""
res = 0
for each_digit in num[::-1]:
res = res * k + int(each_digit)
sign = -1 if sign == k - 1 else 1
return res * sign
def helper(self, nums: List[int], k: int) -> int:
"""
Use k-base to find the single number
"""
# base-k
res, res_sign = '0', 0
for each_num in nums:
each_num_base_k, sign = self.decimal_to_k(each_num, k)
res = self.basek_xor(res, each_num_base_k, k)
res_sign = (sign + res_sign) % k
return self.k_to_decimal(res, res_sign, k)
def singleNumber(self, nums: List[int]) -> int:
return self.helper(nums, 2)