一个整型数组 nums
里除 3 个数字之外,其他数字都出现了两次,请写程序找出这3个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)
方法:
受到剑指 Offer 56的启发,我们寻找一种方法将出现一次的3个数,拆成1个和2个分别放在两组中,其他出现两次的数字被分到相同的组中
定理:我们知道,如果任意的三个数的异或值为0,则若其中一个数在任意的第 n 位上为 1,那么另两个数在第 n 位上必然一个为 1,另一个为 0(否则这三个数的异或值就不会为0),因而当出现 三个数异或值为0 时,可以根据第 n 位为1,可以将这三个数分成两部分
假设这三个不同的数是:A、B、C,则它们的异或值为 X = A
⊕
\oplus
⊕ B
⊕
\oplus
⊕ C,X 的值可能为 0(如 3^5^7=1
),也可能不为 0(如 3^5^6=0
)。当 X 的值不为 0 时,很难找到一种方法,将原来的数组划分为两部分,使得这三个数不都在同一部分。因此,要先对原来的数组进行一次替换:将数组每个数
n
u
m
i
num_i
numi 与 X 进行异或。这样原来的三个数就变成了:B
⊕
\oplus
⊕ C、A
⊕
\oplus
⊕ C、A
⊕
\oplus
⊕ B,记 a = B
⊕
\oplus
⊕ C、b = A
⊕
\oplus
⊕ C、c = A
⊕
\oplus
⊕ B。新的异或值 x = a
⊕
\oplus
⊕ b
⊕
\oplus
⊕ c = 0(出现了三个数的异或值为0的情况)
由于 A、B、C 互不相等,显然它们间的异或值 a、b、c 都不为0,且互不相等(证:若a等于b,则 0 = a ⊕ \oplus ⊕ b = (B ⊕ \oplus ⊕ C) ⊕ \oplus ⊕ (A ⊕ \oplus ⊕ C) = A ⊕ \oplus ⊕ B ,与 A ⊕ \oplus ⊕ B != 0自相矛盾)
设 f(n)
为 n 的二进制表示中最低位 1 的位置,则 f(a)、f(b)、f(c)
这三个数中有且只有两个数相等(证明:不妨设 f(a)、f(b)、f(c)
中最小的是 f(a)
,根据上面的定理,a
⊕
\oplus
⊕ b
⊕
\oplus
⊕ c = 0, 若 f(a)
的值为 k, 表示 a 在第 k 位上为1,则 b、c 在第 k 位上必然是一个为 1,一个为0,不妨设 b 在 k 位为 1 ,则根据 f(n)
的定义以及 f(a)
最小的假设,可得 f(b) = f(a)
,f(c) > f(a)
)
因此,新数组的所有元素
n
e
w
n
u
m
i
newnum_i
newnumi 对应的
f
(
n
e
w
n
u
m
i
)
f(newnum_i)
f(newnumi) 的总异或值等于 f(c)
假设 f(a) = f(b)
,上面求出了 f(c) = m
$\to $ c 在第 m 位上为 1,不妨设 b 在第 m 位也为 1 ,则 a 在第 m 位为 0。根据第 m 位(第f(c)
位)是否为 1,可将新数组分为两部分(一部分包含元素 b=A
⊕
\oplus
⊕C 和 c=A
⊕
\oplus
⊕B 及其它成对元素,另一部分包含元素 a=B
⊕
\oplus
⊕C 及其它成对元素),每部分的异或值恰好都是 B
⊕
\oplus
⊕ C,故可以求出数A(等于B
⊕
\oplus
⊕ C
⊕
\oplus
⊕ X = B
⊕
\oplus
⊕ C
⊕
\oplus
⊕ (A
⊕
\oplus
⊕ B
⊕
\oplus
⊕ C) = A)
最后将数 A 放入原数组,问题转为“只有两个数出现一次”的情况,利用剑指 Offer 56的方法算出另两个数
int f(int n) { // lowbit
return n&(-n);
}
vector<int> three_unique_number(vector<int>& nums) {
int n = nums.size();
int X = 0; // X = A ^ B ^ C
for(auto i:nums) X^=i;
// a = A ^ B, b = A ^ C, c = A ^ B
// x = a ^ b ^ c == 0
int fx = 0; // fx = f(a) ^ f(b) ^ f(c) = f(c) ( f(a)、f(b)、f(c) 这三个数中有且只有两个数相等, 设f(a)=f(b))
for(int i = 0; i < n; ++i) {
fx ^= f(nums[i] ^ X); // 替换原数组num为新数组newnum, newnum[i] = nums[i] ^ X
// 为了节省空间,每次要查看新数组newnum时可以遍历一遍原数组num实时生成新数组
}
// 此时fx = f(c)
int A = X;
// 根据新数组的元素第 f(c) 位是否为 1,可将新数组分为两部分, 一部分包含元素 b=A^C 和 c=A^B 及其它成对元素,另一部分包含元素 a=B^C 及其它成对元素
// 每部分的异或值恰好都是 B ^ C,故可以求出数A(等于B^C^X=B^C^(A^B^C)=A)
for(int i = 0; i < n; ++i) {
// nums[i] ^ X 为新数组newnum的元素,为了节省空间,每次要查看新数组newnum时可以遍历一遍原数组num实时生成新数组
if((nums[i] ^ X) & fx) {
A ^= nums[i] ^ X;
}
}
// 此时求出了 A
// 将数 A 放入原数组,问题转为“只有两个数出现一次”的情况,利用剑指offer56题方法singleNumbers算出另两个数 B、C
int xorsum = X^A ;
int div = xorsum & (-xorsum);
int B = 0, C = 0;
for(auto i:nums) {
if(div & i) {
B ^= i;
} else {
C ^= i;
}
}
if(div & A) {
B ^= A;
} else {
C ^= A;
}
return vector<int>{A, B, C};
}
int main(vector<int>& nums) {
vector<int> nums = {2, 2, 4, 4, 6, 6, 21, 21, 23, 521, 666};
vector<int> ans = three_unique_number(nums);
for(auto a:ans) {
cout<<a<<" ";
}
cout<<endl;
// 521 23 666
return 0;
}
参考:http://www.cppblog.com/flyinghearts/archive/2013/03/21/198695.html