只出现一次的数字
题目描述
给定一个非空整数数组,除了某个元素只出现一次以外,其余每个元素均出现两次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性
时间复杂度。 你可以不使用额外空间
来实现吗?
分析
利用两个相同正数异或抵消
,以及异或具有交换律
的性质,数组里所有数字全部异或,单一的数字
便会被剩下。时间复杂度为O(n)
,空间复杂度为O(1)
。
代码
class Solution(object):
def singleNumber(self, nums):
"""
:type nums: List[int]
:rtype: int
"""
res = 0
for i in nums:
res ^= i
return res
只出现一次的数字 II
题目描述
给定一个非空整数数组
,除了某个元素只出现一次以外,其余每个元素均出现了三次。找出那个只出现了一次的元素。
说明:
你的算法应该具有线性
时间复杂度。 你可以不使用额外空间
来实现吗?
分析及代码
(算法借鉴于:)
二进制下不考虑进位的加法
:本题为136的拓展,136 题中我们用到了异或运算。实际上,异或运算的含义
是二进制下不考虑进位的加法
,即:0 xor 0=0+0=0
,0 xor 1=0+1=1
,1 xor 0=1+0=1
,1 xor 1=1+1=0(不进位)
。三进制下不考虑进位的加法
:通过定义某种运算#
,使得0 # 1 = 1
,1 # 1 = 2
,2 # 1 = 0
。在此运算规则下,出现了3
次的数字的二进制所有位全部抵消为0
,而留下只出现1
次的数字二进制对应位为1
。因此,在此运算规则下将整个arr
中数字遍历加和,留下来的结果则为只出现1
次的数字。- 代码分析:
ones ^= num
:记录至目前元素num
,二进制某位出现 1 次(当某位出现 3 次时有ones=1
,与twos = 1
共同表示“出现3
次”);twos |= ones & num
:记录至目前元素num
,二进制某位出现2
次 (当某位出现2
次时,twos=1
且ones=0
);threes = ones & twos
:记录至目前元素num
,二进制某位出现3
次(即当ones
和twos
对应位同时为 1 时three=1
)。one &= ~threes, two &= ~threes
:将 ones, twos 中出现了 3 次的对应位清零,实现 “不考虑进位的三进制加法” 。
class Solution:
def singleNumber(self, nums: [int]) -> int:
ones, twos, threes = 0, 0, 0
for num in nums:
twos |= ones & num # 二进制某位出现1次时twos = 0,出现2, 3次时twos = 1;
ones ^= num # 二进制某位出现2次时ones = 0,出现1, 3次时ones = 1;
threes = ones & twos # 二进制某位出现3次时(即twos = ones = 1时)three = 1,其余即出现1, 2次时three = 0;
ones &= ~threes # 将二进制下出现3次的位置零,实现`三进制下不考虑进位的加法`;
twos &= ~threes
return ones
- 进一步简化:以上过程本质上是通过构建 3 个变量的状态转换表来表示对应位的出现次数:使所有数字“相加”后出现
3N+1
次的位ones = 1
,出现3N
,3N+2
次的位为ones=0
。由于three
其实是ones & twos
的结果,因此我们可以舍弃threes
,仅使用ones
和twos
来记录出现次数。
某位出现 | 1次 | 2次 | 3次 | 4次 | 5次 | 6次 | … |
---|---|---|---|---|---|---|---|
ones | 1 | 0 | 0 | 1 | 0 | 0 | … |
twos | 0 | 1 | 0 | 0 | 1 | 0 | … |
threes | … |
- 代码分析:
- ones = ones ^ num & ~twos:
- 当 num = 1 时,只当 ones=twos=0 时将 ones 置 1,代表出现 3N+1 次;其余置 0,根据 twos 值分别代表出现 3N 次和 3N+2 次;
- 当 num=0 时,ones 不变;
- twos = twos ^ num & ~ones:
- 当 num = 1 时,只当 ones = twos = 0 时将twos 置 1,代表出现 3N+2 次;其余置 0,根据 ones 值分别代表出现 3N 次和 3N+1 次。
- 当 num=0 时,twos 不变。
- ones = ones ^ num & ~twos:
class Solution:
def singleNumber(self, nums: [int]) -> int:
ones, twos = 0, 0 #出现一次的位,和两次的位
for num in nums:
ones = ones ^ num & ~twos #既不在出现一次的ones,也不在出现两次的twos里面,我们就记录下来,出现了一次,再次出现则会抵消
twos = twos ^ num & ~ones #既不在出现两次的twos里面,也不再出现一次的ones里面(不止一次了),记录出现两次,第三次则会抵消
return ones
只出现一次的数字 III
题目描述
给定一个整数数组 nums
,其中恰好有两个元素只出现一次
,其余所有元素均出现两次。 找出只出现一次的那两个元素。
示例 : 输入: [1,2,1,3,2,5] 输出: [3,5]
注意:
结果输出的顺序并不重要,对于上面的例子, [5, 3] 也是正确答案。
你的算法应该具有线性时间复杂度。你能否仅使用常数空间复杂度来实现?
分析
我们可以利用双层循环以O( )的时间复杂度,O(1)的空间复杂度来解决,但是不符合要求;当然我们也可以利用Hash字典数据结构在O(n)的时间复杂度,O(n)的空间复杂度来解决,但是依然不符合要求;
于是我们根据163题中异或的思想来解决:
- 全部元素异或消掉出现两次的数字. 异或的结果为
s
. - 寻找
s
的lowbit
值.lowbit(s)
为s
的二进制表达式中最右边的1所对应的值
. 因此lowbit(s)
二进制表达式中只有一个bit 1
.
lowbit(s) = s & -s
例如: s=1010
lowbit(s) = 1010 & 0110 = 0010 = 2
- 用
lowbit(s)
将数组分成两组. 一组中,元素A[i] & lowbit(s) == lowbit(s)
, 即包含lowbit(s)的bit 1
. 剩余的是另一组.
而且,两个不同数也一定分在不同组
. 因为异或值s
中的bit1
就是因为两个数字的不同而贡献的. 同一组的元素再异或求出不同数字
. 出现两次的数字
, 肯定出现同一组
,异或后消除掉
.也就是说结果二进制只存在一个1
,就是n
最低位的1
.
代码
class Solution(object):
def singleNumber(self, nums):
"""
:type nums: List[int]
:rtype: List[int]
"""
s = 0
for i in nums:
s ^= i
mask = s&(-s)
res1 = 0
res2 = 0
for i in nums:
if i&mask == mask:
res1 ^= i
else:
res2 ^= i
return res1,res2