链接:https://leetcode-cn.com/problems/single-number-ii/
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
输入: [2,2,3,2]
输出: 3
示例 2:
输入: [0,1,0,1,0,1,99]
输出: 99
思路:
用哈希直接记录每个元素出现的次数,最后遍历哈希表找到次数等于 1 的那个元素即可。但在这种思路依然不符合题意,因为我们用了线性的空间,同时我们还用了两次遍历,所以不是最优解。
时间复杂度:O(n)
空间复杂度:O(n)
class Solution:
def singleNumber(self, nums: List[int]) -> int:
hash={}
for i in nums:
if i not in hash:
hash[i]=1
else:
hash[i]+=1
for k,v in hash.items():
if v!=3:
return k
思路:
因为数组中每个元素都出现了三次,只有一个元素出现了一次,所以可以把所有元素去重之后的和乘以 3,之后减去所有元素之和,最后除以 2 即可找到唯一出现的元素。
3×(a+b+c)−(a+a+a+b+b+b+c)=2c
时间复杂度:O(n),遍历输入数组。空间复杂度:O(n)
class Solution:
def singleNumber(self, nums: List[int]) -> int:
return (3*sum(set(nums))-sum(nums))//2
思路:
从题意可以知道,我们想找到唯一出现 1 次的元素,其余元素都出现了3 次,所以我们是否可以让一个元素出现 3 次的时候自动抵消为 0,这样最后就只剩下只出现 1 次的元素。
假设我们的数组为 nums=[2,2,2,3],对数组进行如下操作:
2 = 10
2 = 10
2 = 10
3 = 11
二进制中 1 的个数: 41
对 3 求余数: 11
在上面的运算中我们是这样做的,首先统计所有数字的各个二进制位中的 1 的个数,求和,之后对 3 求余,最后结果就是那个只出现一次的元素。
当利用三个 bit 位来实现三进制的时候,这样的有限状态自动机有三种状态,即对3的余数分别为0,1,2,转换为二进制即是00,01,10。
若输入二进制的位是0,则状态不变;
若输入二进制的位是1,则按照下图进行转换;
有限状态自动机转换图:
x | y | num | x_new | y_new | |
---|---|---|---|---|---|
1 | 0 | 0 | 0 | 0 | 0 |
2 | 0 | 1 | 0 | 0 | 1 |
3 | 1 | 0 | 0 | 1 | 0 |
4 | 0 | 0 | 1 | 0 | 1 |
5 | 0 | 1 | 1 | 1 | 0 |
6 | 1 | 0 | 1 | 0 | 0 |
推导过程和文章 有限自动状态机:利用位实现三进制,并推广到通用解法 一样,这里我们再写一遍,不清楚的朋友可以去看下这篇文章:
x_new=(~x & y & num) | (x & ~y & ~num)
y_new=(~x & ~y & num) | (~x & y & ~num) = ~x & (y ^ num)
x=x_new
y=y_new
化简后,就好代码中一致了。
y_new = ~x & (y ^ num)
x_new = ~y_new & (x ^ num)
时间复杂度:O(n)
空间复杂度:O(1)
class Solution:
def singleNumber(self, nums: List[int]) -> int:
x=y=0
for i in range(len(nums)):
y = (y ^ nums[i]) & ~x
x = (x ^ nums[i]) & ~y
return y
这道题是 Google 的面试题,要求空间复杂度为 O(1),我自己也是理解了很久,所以总结出来希望对大家有帮助。同时,我也是 Google 得不到的人,希望 Google 可以得到在看文章的你。