题目:
给一个integer数组,每个元素出现3次,除了某个元素,找只出现一次的元素。
对于这个问题,我们希望元素出现了k次(这里为3),就变成0。那么剩下的,就是我们要找的元素。
所以,我们的目标就是,如何能让某个元素累计出现了k次,就变成0。
首先要明白一点,integer在计算机中是以bit的形式存储的,所以我们可以应用位运算操作。
为了简化问题,假设array中的元素只有一位(数组中的值要么是0,要么是1),我们初级目标是来统计1出现的次数,当1出现了k次,计数器的值为0(跟题目出现了k次就置0对应)。
为了统计1次数的次数,我们需要一个计数器,计数器假设有m位(xm,...,x1)。(x1为最低位)
对于这个计数器:
1.初始状态为0。
2.当碰到 0 时,计数器的值不变。
3.当碰到 1 时,计数器的值+1。
4. 计数器的位数 m > logk。
1. 问题:如何让这个计数器实现2,3功能。
假设我们的计数器有3位。
初始状态为:
x3 x2 x1
0 0 0
在这个状态下,当我们碰到0 的时候,计数器不变;碰到1的时候,计数器要变成1,可以通过 x = x ^ i 实现。(^为异或符号)
碰到1之后状态变为:
x3 x2 x1
0 0 1
再碰到0的时候,x = x^i 依旧满足要求,但碰到1的时候,x1 = x1 ^ 1 = 0, 但此时要发生进位操作了(x2要变成1)
那x2在什么情况要变成1呢,当且仅当x1 = 1, 且i = 1的时候。
再考虑,x3什么时候要变化呢,当且x2 = 1, x1 = 1, 且i = 1的时候。
所以可以归纳出:
xm = xm ^ (xm-1 & ... & x1 & i);
xm-1 = xm-1 ^(xm-2 & ... & x1 & i);
...
x1 = x1 ^ i;
此时,计数器可以实现了2,3要求了。
2. 问题:现在实现的这个计数器,只能当k = 2的m次的时候,才会置0。
比如且当m = 3 的时候,只有统计1 出现8次的时候,计数器的值才可以变成0 。
如何让k < 2的m次就置0呢?
所以,如何有一个cut操作,当计数器的值累计到了k, 就重新置0 呢?
所以,我们需要一个步骤,将xm,...,x1 与一些变量(称为mask),进行and 操作。
xm = xm & mask, ..., x1 = x1 & mask.
我们需要这个mask,当 计数器的值为k的时候,进行位运算时候,mask为0(这样进行and,计数器的所有位就变成0了)
当计数器的值不为k的时候,mask为1(这样进行and,计数器的所有位保持不变)
mask可以这样构造:
k 的二进制表示为: km,..., k1.
mask = ~(x1' & x2' & ... xm'), where xj' = xj if kj = 1 and xj' = ~xj if kj = 0 (j = 1 to m).
比如:k = 5: k1 = 1, k2 = 0, k3 = 1, mask = ~(x1 & ~x2 & x3);
假设此时计数器状态为这样:
x3 x2 x1
1 0 0
~(x1 & ~ x2 & x3) = 1, 所以,xi & mask 后,都不会发生变化
假设此时计数器状态为:
x3 x2 x1:
1 0 1
~(x1 & ~ x2 & x3) = 0,所以, xi & mask后,就都置0了。
而且:当且仅当计数器状态为101的时候,才置0。
现在,可以让它提前置0 了。
3. 扩展: 从 1 位 到 32位
当位数变多了,我们需要为每一位创建一个计数器吗?
答案是不需要的。
通过使用m 个 32位 的integer 取代 32个 m 位的integer, 能够联合进行位计数。(因为不同bit上的操作都是相互独立的)
现在的计数器长这样。
bit32 bit31 ... bit 1
x3
x2
x1
4. 最后:重复k次的消掉了,但从哪里获得那个single number 呢?
假设我们的single number 是 1 0 0
别的元素出现k次,single number出现p次。
当p = 1的时候:
最后我们的计数器长这样:
bit32 bit31 ... bit3 bit2 bit1
x3
x2
x1 0 0 ... 1 0 0
当p = 2 的时候:
我们的计数器长这样:(x3为高位,相当于 100 + 100)
bit32 bit31 ... bit3 bit2 bit1
x3
x2 1 0 0
x1 0 0 ... 0 0 0
当p = 3 的时候:
bit32 bit31 ... bit3 bit2 bit1
x3
x2 1 0 0
x1 0 0 ... 1 0 0
当p = 4 的时候:bit32 bit31 ... bit3 bit2 bit1
x3 1 0 0
x2 0 0 0
x1 0 0 ... 0 0 0
所以?归纳出该从哪里找single number了吧?
将single number出现的次数p 转成二进制形式,假设为pm, .. p1
假设pi = 1, 那么 xi 就是single number的二进制表示,返回xi即可。
此算法的时间复杂度为O(n*logk) ,空间复杂度为 O(logk)
代码:
public int singleNumber(int[] nums) {
return singleNumber(nums, 3, 1);
}
// all elements appears k times except one appears p times, find that one
public int singleNumber(int[] nums, int k, int p) {
int m = bit_length(k);
int[] counter = new int[m];
int mask = 1;
for (int n : nums) {
// counting
for (int index = m - 1; index >= 0; index--) {
int sum = n;
for (int innerIndex = index - 1; innerIndex >= 0; innerIndex--) {
sum &= counter[innerIndex];
}
counter[index] ^= sum;
}
//calculating mask
mask = ~0; // pay attention here!
int nowK = k;
for (int i = 0; i < m; i++) {
if ((nowK & 1) == 1) {
mask &= counter[i];
} else {
mask &= ~counter[i];
}
nowK >>= 1;
}
mask = ~mask;
for (int i = 0; i < counter.length; i++) {
counter[i] &= mask;
}
}
// choose return
p %= k;
for (int i = 0; i < m; i++) {
if ((p & 1) == 1) {
return counter[i];
} else {
p >>= 1;
}
}
return -1;
}
public int bit_length(int bits ) {
int res = 0;
while (bits > 0) {
bits >>= 1;
res++;
}
return res;
}
参考:
https://leetcode.com/discuss/31595/detailed-explanation-generalization-bitwise-operation-numbers