[137][中等] 只出现一次的数字 II
题目描述
137. 只出现一次的数字 II 剑指 Offer 56 - II. 数组中数字出现的次数 II
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性时间复杂度。 你可以不使用额外空间来实现吗?
示例 1:
输入: [2,2,3,2]
输出: 3
示例 2:
输入: [0,1,0,1,0,1,99]
输出: 99
解题思路
首先题目是有空间限制的, 不使用额外的空间, 否则使用哈希表就能很轻松的解决问题.
要求数字中的数字有一个上限, 这样数字对应的位长就有固定的长度. 将每个数字转换成二级制, 然后对每一位进行考虑.
逐位循环
单独的看其中某一位, 如果只出现一次的数字, 在这一位上为0, 则其他所有数字在这一位上为1的数量一定为3的倍数.
因此, 统计每一位为1的数量, 然后模3取余数, 如果余数不为0, 那么单独出现的那个数在这一位上为1, 找到所有为1的位就得到了那个数字的二级制表示.
class Solution:
def singleNumber(self, nums: List[int]) -> int:
counts = [0] * 32 # 数字位数长度限制, 最后一位表示符号
for num in nums:
for i in range(32):
counts[i] += num & 1
num >>= 1 # 这样写减少移动的次数
res = 0
for i in range(32):
if counts[i] % 3 != 0:
res += 1 << i
return res if counts[31] % 3 == 0 else ~(res ^ 0xffffffff) # 考虑值为负数的情况
位计算
上面是循环地对每一位计算1出现的次数, 但我们也可以使用位运算的便利性, 同时对所有位进行计算, 在本题目中, 这种计算相当于三进制的加法.
考虑进位, 可以将这种加法表示为如下图所示的状态机:

有向边上的数字为要加上的数值, 节点中的数字为状态. 因此可以得到如下的状态表:
state
once
twice
0
0
0
1
1
0
2
0
1
这张表的state
就是图中的状态. 由于我们要用二级制机制来表示三级制的加法, 为此构造出once
和twice
两个辅组的二级制缓存值, 用来表示三级制中的1, 2.
现在我们考虑, 如何以once
和twice
代替上图, 表示状态机, 而对于二进制, 就转化成了位运算的问题.
once
有两种取值, 0
和1
, 结果为1
的状态有两种:
input=1, once=0, twice=0
input=0, once=1, twice=0
twice
有两种取值, 0
和1
, 结果为1
的状态有两种:
input=1, once=1, twice=0
input=0, once=0, twice=1
因此once
和twice
的状态转移方程为:
once = (input ^ once) & (~twice)
twice = (input ^ twice) & (once ^ twice)
代码为:
class Solution:
def singleNumber(self, nums: List[int]) -> int:
once, twice = 0, 0
for num in nums:
new_once = (num ^ once) & (~twice)
twice = (num ^ twice) & (once ^ twice)
once = new_once
return once
最后更新于
这有帮助吗?