数组中大部分数字都重复出现了k次,这里不妨叫k复制数组。
面试题56-1:二复制数组中只出现一次的两个数字
一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
用按位异或运算,一个数字按位异或它自己就是0,然后0异或某个只出现一次的数字还是那个数字。按位异或运算具有交换性,所以数组里所有数字按位异或最后得到的就是那两个只出现一次的数字按位异或的结果。
考虑将整个数组拆成两个只有一个只出现一次数字的二复制数组,可以根据最后的两个数字按位异或结果(即所有数字的按位异或结果),找结果中是1的某一位(表示这两个数字中这一位不同),作者的做法是用最低的为1的位,按这一位是1还是0来分别在两个变量上做异或运算,逻辑上也就相当于分到了两个数组中去,且相同数字一定在同一数组里。
#include<bits/stdc++.h>
using namespace std;
//判断数字num的第indexBit位是不是1
bool IsBit1(int num, unsigned int indexBit) {
num = num >> indexBit;//indexBit从0开始计数,右移indexBit使其变成最低位
return (num & 1);//和最低位1相与即可
}
//找到num从右边数起第一个是1的位
unsigned int FindFirstBitIs1(int num) {
int indexBit = 0;//计数(接下来要检查的位)
//当前最低位是0,且要检查的位数没有超过极限位数8x字节数
while(((num & 1) == 0) && (indexBit < 8 * sizeof(int))) {
num = num >> 1;//右移1位使下一位成为最低位
++indexBit;//下次循环要检查的位
}
return indexBit;//将这个位数返回,如果没找到那就是8*sizeof(int)
}
//在二复制数组中找两个只出现一次的数字,存入后两个参数指示的内存空间
void FindNumsAppearOnce(int data[], int length, int* num1, int* num2) {
if(data == nullptr || length < 2)//输入合法性检查,最少应该2个数字
return;
int resultExclusiveOR = 0;//存总的异或结果,注意初始化是0才在和第一个异或时使其不变
for(int i = 0; i < length; ++i)
resultExclusiveOR ^= data[i];//挨个做^异或运算
//在结果中找最低的是1的那一位,返回从0开始计数的位数
unsigned int indexOf1 = FindFirstBitIs1(resultExclusiveOR);
*num1 = *num2 = 0;//两个数字将按照刚刚找到的那一位异或出来
for(int j = 0; j < length; ++j) {//再次遍历数组中的每个数
if(IsBit1(data[j], indexOf1))//如果这个数的那一位是1
*num1 ^= data[j];//就将这个数异或到第一个变量上
else//反之,在逻辑上属于另一个子数组
*num2 ^= data[j];//异或到另一个变量上
}
}
int main() {
int data[] = { 2, 4, 3, 6, 3, 2, 5, 5 };
int n1,n2;
FindNumsAppearOnce(data,sizeof(data)/sizeof(int),&n1,&n2);
cout<<n1<<","<<n2<<endl;//6,4
return 0;
}
面试题56-2:三复制数组中唯一只出现一次的数字
在一个数组中除了一个数字只出现一次之外,其他数字都出现了三次。请找出那个吃出现一次的数字。
如果把所有出现三次的数字二进制的每一位分别加起来,那么得到的每一位都能被3整除。所以如果把整个数组中的所有数字每一位都分别加起来,最后不能被3整除的那一位就是特殊数字中是1的位。
#include<bits/stdc++.h>
using namespace std;
//在三复制数组中找只出现一次的一个数字
int FindNumberAppearingOnce(int numbers[], int length) {
if(numbers == nullptr || length <= 0)//输入合法性
throw new exception();
int bitSum[32] = {0};//4字节32位int,存32整个数组按位加之后每一位的情况
//注意,这里按作者的写法是反过来存的,有点绕
//如bit[31]其实是最低bit位的的值相加的结果
//这从内存的角度看也可以理解...
//总之数组下标高的存的是整数的低位加和
for(int i = 0; i < length; ++i) {//遍历整个数组
int bitMask = 1;//比特掩码,用于取int数特定位置的bit值
for(int j = 31; j >= 0; --j) {
int bit = numbers[i] & bitMask;//取这一位的值
if(bit != 0)//如果是1
bitSum[j] += 1;//将数组中对应位置+1
bitMask = bitMask << 1;//掩码左移一位,下次循环要取那一位的值
}
}
int result = 0;//最终结果,先将所有位置置0
for(int i = 0; i < 32; ++i) {//从高位向低位看
result = result << 1;//左移,之后最低位是要考察和设置的位
result += bitSum[i] % 3;//如果是3的倍数就+0,否则+1
}
return result;
}
int main() {
int numbers[] = { 1024, -1025, 1024, -1025, 1024, -1025, 1023 };
cout<<FindNumberAppearingOnce(numbers,sizeof(numbers)/sizeof(int))<<endl;//1023
return 0;
}