Q: Given an array of integers, every element appears three times except for one. Find that single one.
如果是其余元素均出现两次,这个题目很容易解决,我们直接可以用xor,把所有的数异或之后的结果,便是出现一次的数字。但是这个题目其余元素出现的次数是三次,如果数组中的元素都是三个三个出现的,那么从二进制表示的角度,每个位上的1加起来,应该可以整除3。
基本方法:
如果有一个数x只出现一次,会是什么情况呢?
如果某个特定位上的1加起来,可以被3整除,说明对应x的那位是0,因为如果是1,不可能被3整除
如果某个特定位上的1加起来,不可以被3整除,说明对应x的那位是1
根据上面的描述,我们可以开辟一个大小为32的数组,第0个元素表示,A中所有元素的二进制表示的最低位的和,依次类推。
int get_high_bit_one(int num){
for (int i = 31; i >=0; --i){
if (!(num & (1<<i)))
continue;
return i;
}
}
int find_once_number(int arr[], int len){
int* bit_arr = new int[32];
int num = 0;
if (bit_arr != NULL){
memset(bit_arr, 0, 32*sizeof(int));
for (int index = 0; index < len; ++index){
int bit_nums = get_high_bit_one(arr[index]);
for (int bit_idx = 0; bit_idx <= bit_nums; ++bit_idx){
bit_arr[bit_idx] += (arr[index] & (1<<bit_idx))>>bit_idx;
}
}
for (int i = 0; i < 32; ++i){
if (bit_arr[i]%3)
bit_arr[i] = 1;
else
bit_arr[i] = 0;
}
for (int i = 0; i < 32; ++i){
num += bit_arr[i]*(1<<i);
}
delete[] bit_arr;
}
return num;
}
位操作is coming…
有没有更加简洁的方法呢?我们可以从位操作上入手这个问题。用二进制模拟三级制的运算。
B1 | B0 | Input | B1` | B0` |
0 | 0 | 1 | 0 | 1 |
0 | 1 | 1 | 1 | 0 |
1 | 0 | 1 | 0 | 0 |
0 | 0 | 0 | 0 | 0 |
0 | 1 | 0 | 0 | 1 |
1 | 0 | 0 | 1 | 0 |
通过位之间的转换,我们可以得到如下代码.
int singleNumber(int A[], int n) {
int one = 0, two = 0;
for (int i = 0; i < n; i++) {
int one_ = (one ^ A[i]) & ~two;
int two_ = A[i] & one | ~A[i] & two;
one = one_;
two = two_;
}
return one;
}
用ones记录到当前计算的变量为止,二进制1出现“1次”(mod 3 之后的 1)的数位。用twos记录到当前计算的变量为止,二进制1出现“2次”(mod 3 之后的 2)的数位。当ones和twos中的某一位同时为1时表示二进制1出现3次,此时需要清零。即用二进制模拟三进制计算。最终ones记录的是最终结果。
int singleNumber(int A[], int n) {
int ones = 0, twos = 0, xthrees = 0;
for(int i = 0; i < n; ++i) {
twos |= (ones & A[i]);
ones ^= A[i];
xthrees = ~(ones & twos);
ones &= xthrees;
twos &= xthrees;
}
return ones;
}
更好的方法,而且可以扩展到多位计算的方法。
int getSingleNumber_v2(int a[], int len){
if (a == NULL)
return 0;
int x0 = ~0, x1 = 0, x2 = 0, tmp;
for (int i = 0; i < len; ++i){
tmp = x2;
x2 = (x1 & a[i]) | (x2 & ~a[i]);
x1 = (x0 & a[i]) | (x1 & ~a[i]);
x0 = (tmp & a[i]) | (x0 & ~a[i]);
}
return x1;
}
利用上述方法可以解决这个扩展问题:
Q:Given an array ofintegers, every element appears k times except for one. Find that single one who appears l times.
int getSingleNumber_v3(int A[], int len, int k, int l){
if (A == NULL) return 0;
int t;
int* x = new int[k];
memset(x, 0, k*sizeof(int));
x[0] = ~0;
for (int i = 0; i < len; i++) {
t = x[k-1];
for (int j = k-1; j > 0; j--) {
x[j] = (x[j-1] & A[i]) | (x[j] & ~A[i]);
}
x[0] = (t & A[i]) | (x[0] & ~A[i]);
}
return x[l];
}