位运算(异或运算) :数组中数字出现的次数
Posted 炫云云
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了位运算(异或运算) :数组中数字出现的次数相关的知识,希望对你有一定的参考价值。
文章目录
数组中数字出现的次数
一个整型数组 nums 里除两个数字之外,其他数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
示例 1:
输入:nums = [4,1,4,6]
输出:[1,6] 或 [6,1]
示例 2:
输入:nums = [1,2,10,4,1,4,3,3]
输出:[2,10] 或 [10,2]
位运算(异或运算)
设整型数组 nums 中出现一次的数字为 x ,出现两次的数字为 a, a, b, b, … ,即:
n
u
m
s
=
[
a
,
a
,
b
,
b
,
.
.
.
,
x
]
nums = [a, a, b, b, ..., x]
nums=[a,a,b,b,...,x]
异或运算有个重要的性质,两个相同数字异或为 0 ,任何数字与0异或结果是其本身。即对于任意整数 a 有
a
⊕
a
=
0
a \\oplus a = 0
a⊕a=0 。因此,若将 nums 中所有数字执行异或运算,留下的结果则为 出现一次的数字
x
x
x ,即:
a
⊕
a
⊕
b
⊕
b
⊕
.
.
.
⊕
x
=
0
⊕
0
⊕
.
.
.
⊕
x
=
x
\\begin{aligned} & \\ \\ a \\oplus a \\oplus b \\oplus b \\oplus ... \\oplus x \\\\ = & \\ \\ 0 \\oplus 0 \\oplus ... \\oplus x \\\\ = & \\ \\ x \\end{aligned}
== a⊕a⊕b⊕b⊕...⊕x 0⊕0⊕...⊕x x
异或运算满足交换律 a ⊕ b = b ⊕ a a \\oplus b = b \\oplus a a⊕b=b⊕a ,即以上运算结果与 nums 的元素顺序无关。代码如下:
def singleNumber( nums):
z = 0
for num in nums: # 1. 遍历 nums 执行异或运算
z ^= num
print(z)
return z; # 2. 返回出现一次的数字 x
nums = [3,3,4,4,1]
end = singleNumber( nums)
3
0
4
0
1
本题难点: 数组 nums 有 两个 只出现一次的数字,因此无法通过异或直接得到这两个数字。
设两个只出现一次的数字为 x , y x , y x,y ,由于 x ≠ y x \\ne y x=y ,则 x x x 和 y y y 二进制至少有一位不同(即分别为 0 和 1 ),根据此位可以将 nums 拆分为分别包含 x 和 y 的两个子数组。
易知两子数组都满足 「除一个数字之外,其他数字都出现了两次」。因此,仿照以上简化问题的思路,分别对 两子数组 遍历执行异或操作,即可得到两个只出现一次的数字 x , y x , y x,y 。
算法流程:
- 遍历 nums执行异或:
设整型数组
n
u
m
s
=
[
a
,
a
,
b
,
b
,
.
.
.
,
x
,
y
]
nums = [a, a, b, b, ..., x, y]
nums=[a,a,b,b,...,x,y] ,初始化n=0,任何数字与0异或结果是其本身。 对 nums 中所有数字执行异或,得到的结果为
x
⊕
y
x \\oplus y
x⊕y ,即:
n
=
n
⊕
a
⊕
a
⊕
b
⊕
b
⊕
.
.
.
⊕
x
⊕
y
=
0
⊕
0
⊕
.
.
.
⊕
x
⊕
y
=
x
⊕
y
\\begin{aligned} n = & \\ \\ n \\oplus a \\oplus a \\oplus b \\oplus b \\oplus ... \\oplus x \\oplus y \\\\ = & \\ \\ 0 \\oplus 0 \\oplus ... \\oplus x \\oplus y \\\\ = & \\ \\ x \\oplus y \\end{aligned}
n=== n⊕a⊕a⊕b⊕b⊕...⊕x⊕y 0⊕0⊕...⊕x⊕y x⊕y
- 循环左移计算 m :
根据异或运算定义,若整数 x ⊕ y x \\oplus y x⊕y 某二进制位为 1 ,则 x x x 和 y y y 的此二进制位一定不同。换言之,找到 x ⊕ y x \\oplus y x⊕y 某为 1 的二进制位,即可将数组 nums 拆分为上述的两个子数组。根据与运算特点,可知对于任意整数 a 有:
若 a & 0001 = 1 a \\& 0001 = 1 a&0001=1 ,则 a a a 的第一位为 1 ;
若 a & 0010 = 1 a \\& 0010 = 1 a&0010=1 ,则 a a a 的第二位为 1 ;
以此类推……
因此,初始化一个辅助变量 m = 1 m = 1 m=1 ,通过与运算从右向左循环判断,可 获取整数 x ⊕ y x \\oplus y x⊕y 首位 是 1的位置 ,记录于 m m m 中,
举个例子:n = 10 ^ 2 = 1010 ^ 0010 = 1000,第四位为1.如果(n & m)的结果等于0说明n的最低为是0
代码如下:
while n & m == 0: # m 循环左移一位,直到 n & m != 0
m <<= 1
-
拆分 nums 为两个子数组
-
分别遍历两个子数组执行异或:
通过遍历判断 nums中各数字和 m 做与运算的结果,可将数组拆分为两个子数组,并分别对两个子数组遍历求异或,则可得到两个只出现一次的数字,代码如下:
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
- 返回值:
返回只出现一次的数字 x, y 即可。
设 nums = [3, 3, 4, 4, 1, 6],以上计算流程如下图所示。
class Solution:
def singleNumbers(self, nums: List[int]) -> List[int]:
x, y, n, m = 0, 0, 0, 1
for num in nums: # 1. 遍历异或
n ^= num
while n & m == 0: # 2. 循环左移,计算 m
m <<= 1
for num in nums: # 3. 遍历 nums 分组
if num & m: x ^= num # 4. 当 num & m != 0
else: y ^= num # 4. 当 num & m == 0
return x, y # 5. 返回出现一次的数字
复杂度分析:
时间复杂度 O(N) : 线性遍历 nums 使用 O(N) 时间,遍历 x ⊕ y x \\oplus y x⊕y 二进制位使用 O ( 32 ) = O ( 1 ) O(32) = O(1) O(32)=O(1)时间。
空间复杂度 O(1): 辅助变量 a , b , x , y 使用常数大小额外空间。
判定字符是否唯一
实现一个算法,确定一个字符串 s 的所有字符是否全都不同。
示例 1:
输入: s = "leetcode"
输出: false
示例 2:
输入: s = "abc"
输出: true
如果你不使用额外的数据结构,会很加分。
位运算(异或运算)
这里就是设置一个值,mask。然后利用每个字符串的唯一性质(unicode),就将1向左移动unicode的值。得到一个二进制的数值(如100000)如果与的答案为0,就说明没有出现过,将这个位置的1或给mask,不同的字符就代表着不同的位置,这个mask就会在不同的位置变为1。然后循环。
class Solution(object):
def isUnique(self, astr):
"""
:type astr: str
:rtype: bool
"""
mask = 0 #假设这个变量占26个bit ,那么我们可以把它看成000...00(26个0),
#这26个bit对应着26个字符,对于一个字符c,检查对应下标的bit值即可判断是否重复。
for i in astr:
move = ord(i) - ord('a')
if mask & (1<<move) != 0: #使用左移运算符1 << move 则可以得到对应下标为1,其余下标为0的数,如字符char = 'c',则得到的数为000...00100
# 将这个数跟mark做与运算,由于这个数只有一个位为1,其他位为0,那么与运算的结果中,其他位肯定是0,
# 而对应的下标位是否0则取决于之前这个字符有没有出现过,若出现过则被标记为1,那么与运算的结果就不为0
return False
else:
mask |= (1<<move) #若之前没有出现过,则对应位的与运算的结果也是0,那么整个结果也为0。
#对于没有出现过的字符,我们用或运算mask |= (1<<move) 将对应下标位的值置为1。
return True
class Solution(object):
def isUnique(self, astr):
"""
:type astr: str
:rtype: bool
"""
from collections import Counter
dic=Counter(astr)
return len(dic.values())==0 or max(dic.values())<2
参考
力扣(LeetCode) (leetcode-cn.com)]
《画解剑指 Offer 》
https://leetcode-cn.com/problems/search-insert-position/solution/te-bie-hao-yong-de-er-fen-cha-fa-fa-mo-ban-python-/
以上是关于位运算(异或运算) :数组中数字出现的次数的主要内容,如果未能解决你的问题,请参考以下文章
剑指 Offer 56 - I. 数组中数字出现的次数(位运算(异或与),Java)
剑指Offer56 - I. 数组中数字出现的次数(位运算)
leetcode中等剑指 Offer 56 数组中数字出现的次数