题目
剑指 Offer 56 - I. 数组中数字出现的次数 - 力扣(LeetCode)
解题思路
异或性质:
0
⊕
0
=
0
(1)
0 \oplus 0=0 \tag{1}
0⊕0=0(1)
0
⊕
1
=
1
(2)
0 \oplus 1=1 \tag{2}
0⊕1=1(2)
1
⊕
0
=
1
(3)
1 \oplus 0=1 \tag{3}
1⊕0=1(3)
1
⊕
1
=
0
(4)
1 \oplus 1=0 \tag{4}
1⊕1=0(4)
结论 ( 1 ) : (1): (1):由 ( 1 ) ( 2 ) (1)(2) (1)(2)知,与 0 0 0异或等于本身
结论 ( 2 ) : 由 (2):由 (2):由(3)(4)$知,与 1 1 1异或等于取反
结论 ( 3 ) : 由 (3):由 (3):由(1)(4)$知,相同异或为 0 0 0
结论 ( 4 ) : 由 (4):由 (4):由(2)(3)$知,相异异或为 1 1 1
同时异或拥有
交换律:
a
⊕
b
=
b
⊕
a
a \oplus b=b \oplus a
a⊕b=b⊕a
结合律:
(
a
⊕
b
)
⊕
c
=
a
⊕
(
b
⊕
c
)
(a \oplus b) \oplus c=a \oplus (b \oplus c)
(a⊕b)⊕c=a⊕(b⊕c)
第一题:假如数组中所有数为
a
1
,
b
1
,
.
.
.
,
a
n
s
,
.
.
.
,
a
n
,
b
n
a_1,b_1,...,ans,...,a_n,b_n
a1,b1,...,ans,...,an,bn
a
i
,
b
i
a_i,b_i
ai,bi为相等的一对,成为对子。
所有数相异的结果可以看做:利用交换律和结合律可以将所有对子先异或,结果为零,最后异或
a
n
s
ans
ans,根据结论
(
1
)
(1)
(1),因此结果为
a
n
s
ans
ans
第二题:假如数组中所有数为
a
1
,
b
1
,
.
.
.
,
a
n
s
1
,
a
n
s
2...
,
a
n
,
b
n
a_1,b_1,...,ans1,ans2...,a_n,b_n
a1,b1,...,ans1,ans2...,an,bn
利用第一题的思想,只需设法将数组分为两组,其中
a
n
s
1
,
a
n
s
2
ans1,ans2
ans1,ans2各在一组。
因为其他对子的值相同,只要按照同一标准分组,对子肯定会分在同一组,从而异或为零,因此不必考虑分组标准对其他元素的影响,只需考虑如果将
a
n
s
1
,
a
n
s
2
ans1,ans2
ans1,ans2分开。
假设:
m
a
s
k
=
a
n
s
1
⊕
a
n
s
2
mask=ans1 \oplus ans2
mask=ans1⊕ans2
如果
m
a
s
k
mask
mask为零,这不可能,根据结论
(
3
)
(3)
(3),与题设矛盾。
如果
m
a
s
k
mask
mask不为零,
m
a
s
k
mask
mask的二进制表示中一定有一位为
1
1
1,假设为
m
a
s
k
i
mask_i
maski
根据结论
(
4
)
(4)
(4),
a
n
s
1
i
,
a
n
s
2
i
ans1_i,ans2_i
ans1i,ans2i一定不相同。由此找到将
a
n
s
1
,
a
n
s
2
ans1,ans2
ans1,ans2分开的标准。
理论上根据
m
a
s
k
mask
mask中任意不为零的
b
i
t
bit
bit位都可以将
a
n
s
1
,
a
n
s
2
ans1,ans2
ans1,ans2区分开。
为了简便,选择
l
o
w
b
i
t
lowbit
lowbit
代码
class Solution {
public:
vector<int> singleNumbers(vector<int>& nums) {
int mask = 0;
vector<int> ans;
int a = 0;
int b = 0;
for(auto& t : nums){
mask ^= t;
}
// lowbit
mask = mask & (~mask + 1);
for(auto& t : nums){
if(t & mask){
a ^= t;
}else{
b ^= t;
}
}
ans.push_back(a);
ans.push_back(b);
return ans;
}
};