问题一:一个数字出现1次,其它出现偶数次
# 问题一 当奇数个数字唯一时:
from functools import reduce
def find_one(arr):
return reduce(lambda x, y: x ^ y, arr)
问题二:两个数字出现1次,其它出现偶数次
# 问题二 当出现奇数个的数字为2个时:
# 主要思路为按位划分为两组,则每组各包含一个出现奇数次的数字,然后再根据异或运算的性质推算即可
def find_one_when_2(arr):
# a ^ b = c 等价于 a^c = b
c = find_one(arr)
index = c & (~c + 1) # 找到最右侧的1
a = 0
for i in arr: # 按照在该位的01值划分数组
if i & index:
a ^= i
a, b = a, c ^ a
return (a, b) if a < b else (b, a) # 返回不重复的数字(a, b), 升序排列
问题三:一个数字出现1次,其它出现3次
# 问题三 当除了一个数只出现一次,其它都出现三次时:
# 进行异或运算时,若某一位结果不为0,则该位不为1的数字出现了奇数次以上
# 为了区分出现一次的数字和出现三次的数字,使用两个位掩码:seen_once 和 seen_twice:
# 仅当 seen_twice 未变时,改变 seen_once。
# 仅当 seen_once 未变时,改变seen_twice。
# https://leetcode-cn.com/problems/single-number-ii/solution/zhi-chu-xian-yi-ci-de-shu-zi-ii-by-leetcode/
def one_and_three(arr):
seen_once = seen_twice = 0
for v in arr:
# 第一次出现时,将v与seen_once相加
# 第二次出现时,从seen_once中移除并赋值给seen_twice:
# 第三次出现时: 从seen_twice中移除
# 既不在出现一次、也不在出现两次的位掩码里面,我们就记录下来,出现了一次,再次出现则会抵消
seen_once = ~seen_twice & (seen_once ^ v) # 滤去出现了两次的位
# 既不在出现两次的位掩码里面,也不再出现一次的位掩码里面(不止一次了),记录出现两次,第三次则会抵消
seen_twice = ~seen_once & (seen_twice ^ v) # 滤去出现了一次的位
return seen_once
问题四:一个数字出现1次,其它出现m次
# 问题四 是上一题的加强版,当其它数字出现了m次
# 相比于哈希法,空间复杂度仅为O(1)
def one_and_more(arr, m):
counts = [0] * 32
for v in arr: # 获取所有数字各二进制位的1出现次数
for j in range(32):
counts[j] += v & 1
v = v >> 1
res = 0
for i in range(32): # 将counts各元素对m求余,则结果为 “只出现一次的数字” 的各二进制位。
res = res << 1
res |= counts[31 - i] % m # 恢复第 i 位的值到 res
# 由于 Python 的存储负数的特殊性,需要先将 0 - 32 位取反(即 res ^ 0xffffffff ),再将所有位取反(即 ~ )。
return res if counts[31] % m == 0 else ~(res ^ 0xffffffff)