SingleNumber类问题的泛化研究
问题概述
在一堆整数中,根据重复次数不同,找出某个特定重复次数的数。
问题分析
既然该问题的结果与数字的重复次数有关,那就一定要计数,普通的求解办法是将每个数看成一个整体进行计数,需要的空间复杂度。而另一个十分有趣的解法是考虑数在计算机里的二进制表示,将数分解到位,按位计数,可以将空间复杂度优化到。
问题详述及按位计数法概述
- 问题详述
给定n个整数,除了一个数字A的重复次数为y外,其余数字的重复次数均为x(x>1)的整数倍,显然y不是x的整除倍,问题就是找出这个数字A。- 算法概述
考虑到整数在计算机中的二进制存储形式,盯住所有数的某一位,若A在这一位上为0,则所有数在这一位的和一定是x的整数倍;若A在这一位上为1,则所有数在这一位的和一定不是x的整数倍。由此便可确定A每一位的值,进而得到A。
算法详述
现在假设这n个数存放在32b的int型数组a[ ]中,然后现在盯住a[i]这个整数的第位,记作 。我们A的第j位记作。我们将n个数上第j位的累加和记作,即
有如下两种情况:
- 若
- 若
即
由上式可得
故
算法实现
- 按位直接计数法,需要知道存储数据的整型类型的位数,下面以32位为例,伪代码如下:
S[32] = {0}
for(i = 0; i < n; i++){
for(j = 0; j < 31; j++){
if(a[i] & 1<<j){
S[j] = (S[j] + 1)%x;//对a数组第j位进行计数
}
}
}
A = 0;
for(j = 0; j < 31; j++){
if(S[j] > 0){
A |= 1<<j;//将A的第j位置1
}
}
- 真值表法实现自动计数,可以进一步优化空间复杂度,而且不需要知道整型位数。重复5的倍数找3次的c语言实现如下:
#include<stdio.h>
int singleNumber(int *arry, int len){
const int *a = arry;
int p=0,q=0,r=0;
int old_p,old_q,old_r;//临时变量
for(int i = 0; i < len; i++){
old_p = p;
old_q = q;
old_r = r;
p = (old_p&~old_q&~old_r&~a[i])|(~old_p&old_q&old_r&a[i]);
q = (~old_p&old_q&~a[i])|(~old_p&a[i]&(old_q^old_r));
r = ~old_p&(old_r^a[i]);
}
return r;
}
int main(){
int a[13] = {5,5,5,2,2,7,7,7,7,7,2,5,5};
printf("%d\n",singleNumber(a,13));
return 0;
}
真值表法详述:
真值表在本题中的表现,着实让我惊艳了一下。用真值表我们可以实现我们自定义的运算规则。
分析讲解:如果用二进制数来当计数器,那么计数x次,最少需要m位二进制数。
则我们需要m个int型变量,用m个变量对应的第j位逻辑上组成一个m位的计数器,记作。
计数器根据输入的计数规律为:,但是该计数规律并非是用加法和取余实现的,而是通过真值表得到的位运算实现的。计数结束后,m个变量的值要么是结果A,要么是0。
以重复5的倍数找3次为例,需要3个int型变量,3个变量的对应第j位组成一个3位计数器。计数器的计数过程为:
我们分别用p,q,r来表示计数器的3个位,input相当于,则最后计数结束后,p位对应的变量值为0,而q,r对应的变量为结果A。
用真值表方法求解:
p | q | r | input | P | Q | R |
---|---|---|---|---|---|---|
0 | 0 | 0 | 0 | 0 | 0 | 0 |
0 | 0 | 1 | 0 | 0 | 0 | 1 |
0 | 1 | 0 | 0 | 0 | 1 | 0 |
0 | 1 | 1 | 0 | 0 | 1 | 1 |
1 | 0 | 0 | 0 | 1 | 0 | 0 |
0 | 0 | 0 | 1 | 0 | 0 | 1 |
0 | 0 | 1 | 1 | 0 | 1 | 0 |
0 | 1 | 0 | 1 | 0 | 1 | 1 |
0 | 1 | 1 | 1 | 1 | 0 | 0 |
1 | 0 | 0 | 1 | 0 | 0 | 0 |
我们根据当前的p,q,r与输入input经过某种关系映射后得到P,Q,R,所以理论上因变量P,Q,R的自变量为p,q,r,input。
如下所示:
即
以上是一位的计数运算,而所有位的运算都是相同的。
实际编程中p,q,r,input都是32位int型变量,因为计算机中的位运算就是对变量进行按位的统一的位运算,即对变量的所有位进行相同的运算,故而上述算法不用分解到位,单独运算,再进行整合。
不过对于不同的重复次数需要画不同的真值表,然后再算计数表达式,没有统一的公式。
如果没有对效率有特别严格的要求,建议使用方法一。另外重复2的倍数找一次,使用异或运算即可,也属于真值表法,不过这种情况比较好记。