题目描述
该类题目书中共有两题,以下为题目描述:
- 一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
Leetcode56-I:数组中数字出现的次数 - 在一个数组 nums 中除一个数字只出现一次之外,其他数字都出现了三次。请找出那个只出现一次的数字。
Leetcode56-II:数组中数字出现的次数 II
算法分析
- 因为题目限制了O(n)的时间复杂度,所以排序或者map去重不能用来解决本题。考虑到任何一个数字异或自身得到的是0,可以通过异或的方式去掉数组中重复的数字。
具体思路:异或数组中所有数字,最终得到的结果xorRes等于两个singleNumber异或的值,所以可以通过按位与求出xorRes中右边第一个等于1的二进制位。根据该二进制位将原数组分组,在该二进制位上等于1的为一组,等于0的为另一组。易知重复的数字一定会在同一组,不同的数字一定会在不同的组,因此可以对两个组分别异或求和,最终得到的就是两个singleNumber。 - 本题因为相同的数字都有三个,所以不能通过异或运算解决。因为只有一个singleNumber,所以统计数字的各二进制位1的个数,用一个长为32的数组保存(
1
<
n
u
m
s
[
i
]
<
2
31
1 < nums[i] < 2^{31}
1<nums[i]<231)。最终对数组的每一个元素模3,再将二进制位拼起来,得到的就是唯一的singleNumber。
本题可以用有限状态自动机[1]简化时间复杂度和空间复杂度。
-
首先确定状态,对每一个二进制位来说,数字1的个数共有三种状态:0、1、2(模3),所以可以用两位二进制位表示状态:00、01、10。
-
状态转移规则
低位用one表示,高位用two表示,二进制位的值用bit表示,
统计one:
if two == 1,one只能为0
if two == 0,if bit == 0,则one不变
if two == 1,if bit == 1,则one取反
⇒ \Rightarrow ⇒ if two == 1,则one不变
if two == 0,one ^ bit
⇒ \Rightarrow ⇒ one = one ^ bit & ~two
统计two(此时one已经更新):
if one == 1,two只能为0
if one == 0,if bit == 0,说明无进位,two不变
if one == 0,if bit == 1,说明有进位,two取反
发现计算two的公式其实和one是一样的:two ^ bit & ~one -
对于32个二进制位,状态转移规则都是一样的,因此可以直接计算32位数。
复杂度分析
问题规模为数组元素个数n
- 时间复杂度:O(n),需要遍历数组
空间复杂度:O(1) - 时间复杂度:O(n),需要遍历数组
空间复杂度:O(1)
Golang代码如下
// 题一
func singleNumbers(nums []int) []int {
xorRes, count := 0, 1
x, y := 0, 0
for _, v := range nums {
xorRes ^= v
}
for xorRes & count == 0 {
count <<= 1
}
for _, v := range nums {
if v & count != 0 {
x ^= v
} else {
y ^= v
}
}
return []int{x, y}
}
// 题二
func singleNumber(nums []int) int {
ones, twos := 0, 0
for _, v := range nums {
ones = ones ^ v & ^twos
twos = twos ^ v & ^ones
}
return ones
}