目录
三、剑指 Offer - II. 数组中数字出现的次数 II
一、剑指 Offer 15. 二进制中 1 的个数
1.1 题求
1.2 求解
法一:移位运算符
# 28ms - 97.36%
class Solution:
def hammingWeight(self, n: int) -> int:
# 汉明重量
hw = 0
for _ in range(32):
# 若末位为 1, 则汉明重量 +1
if n & 1 == 1:
hw += 1
# 右移一位 (左移也行 <<= )
n >>= 1
return hw
官方说明
class Solution:
def hammingWeight(self, n: int) -> int:
res = 0
while n:
res += n & 1
n >>= 1
return res
# 28ms - 97.36%
class Solution:
def hammingWeight(self, n: int) -> int:
res = 0
while n:
res += 1
n &= n-1 # 每轮消去数字 n 最右边的 1
return res
1.3 解答
参考资料:
《剑指 Offer 第二版》
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5vk1l3/
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5vgz7m/
二、剑指 Offer 56 - I. 数组中数字出现的次数
2.1 题求
2.2 求解
法一:哈希集合
# 36ms - 98.58% - 可惜不符合要求
class Solution:
def singleNumbers(self, nums: List[int]) -> List[int]:
hashset = set()
for n in nums:
if n not in hashset:
hashset.add(n) # 未出现, 则加入
else:
hashset.remove(n) # 出现过, 则移除
return list(hashset) # 最后剩下的就是仅出现过一次的
官方说明
def singleNumber(self, nums: List[int]) -> List[int]:
x = 0
for num in nums: # 1. 遍历 nums 执行异或运算
x ^= num
return x; # 2. 返回出现一次的数字 x
while z & m == 0: # m 循环左移一位,直到 z & m != 0
m <<= 1
for num in nums:
if num & m:
x ^= num # 若 num & m != 0 , 划分至子数组 1 ,执行遍历异或
else:
y ^= num # 若 num & m == 0 , 划分至子数组 2 ,执行遍历异或
return x, y # 遍历异或完毕,返回只出现一次的数字 x 和 y
# 28ms - 99.96%
class Solution:
def singleNumbers(self, nums: List[int]) -> List[int]:
x, y, n, m = 0, 0, 0, 1
for num in nums: # 1. 遍历异或, 结果 n = x ^ y
n ^= num
while n & m == 0: # 2. 循环左移,计算令 n == 1 用于划分子数组的辅助变量 m
m <<= 1
for num in nums: # 3. 遍历 nums 并根据辅助变量 m 分组
if num & m: # 4. 当 num & m != 0, 划分至子数组 1, 执行遍历异或
x ^= num
else: # 4. 当 num & m == 0, 划分至子数组 2, 执行遍历异或
y ^= num
return x, y # 5. 返回出现一次的数字 x, y
2.3 解答
参考资料:
《剑指 Offer 第二版》
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/eubbnm/
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/euj1a1/
三、剑指 Offer - II. 数组中数字出现的次数 II
3.1 题求
3.2 求解
法一:哈希集合 + 哈希表
# 40ms - 88.70%
class Solution:
def singleNumber(self, nums: List[int]) -> int:
hashset = set()
hashmap = {}
# 遍历数组
for n in nums:
# 首次记录
if hashmap.get(n) is None:
hashmap[n] = 0
hashset.add(n)
# 记录 +1
hashmap[n] += 1
# 出现次数为 3 则移除候选数
if hashmap[n] == 3:
hashset.remove(n)
return hashset.pop()
法二:哈希表
# 52ms - 80.87%
class Solution:
def singleNumber(self, nums: List[int]) -> int:
hashmap = {}
# 遍历数组
for n in nums:
# 首次记录
if hashmap.get(n) is None:
hashmap[n] = 0
# 记录 +1
hashmap[n] += 1
# 出现次数为 3 则移除候选数
if hashmap[n] == 3:
del hashmap[n]
# 最后剩下的一对 key:value 即为仅出现 1 次的数
return hashmap.popitem()[0]
官方说明
# 52ms - 80.73%
class Solution:
def singleNumber(self, nums: List[int]) -> int:
'''
考虑数字的二进制形式,对于出现三次的数字,各二进制位出现的次数都是 3 的倍数
因此,统计所有数字的各二进制位中 1 的出现次数,并对 3 求余,结果则为只出现一次的数字
由于二进制只能表示 0, 1,因此需用两个二进制位来表示 3 个状态。设为 two, one
'''
ones, twos = 0, 0
for num in nums:
# 两个二进制位的状态转换
ones = ones ^ num & ~twos
twos = twos ^ num & ~ones
return ones
# 388ms - 23.70%
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 # 更新第 i 位 1 的个数之和
num >>= 1 # 第 i 位 --> 第 i 位
res, m = 0, 3
for i in range(31, -1, -1):
res <<= 1
res |= counts[i] % m # 恢复第 i 位
return res if counts[31] % m == 0 else ~(res ^ 0xffffffff)
3.3 解答
参考资料:
《剑指 Offer 第二版》
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/9hyq1r/
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/9hctss/
四、剑指 Offer 65. 不用加减乘除做加法
4.1 题求
4.2 求解
官方说明
# 32ms - 78.97%
class Solution:
def add(self, a: int, b: int) -> int:
x = 0xffffffff
a, b = a & x, b & x
while b != 0:
# n = a ^ b - 非进位和:异或运算
# c = (a & b) << 1 - 进位:与运算 + 左移一位
a, b = (a ^ b), (a & b) << 1 & x
return a if a <= 0x7fffffff else ~(a ^ x)
4.3 解答
参考资料:
《剑指 Offer 第二版》
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5vz6d1/
https://leetcode-cn.com/leetbook/read/illustration-of-algorithm/5vs8w7/