程序员面试题精选100题(63)-数组中三个只出现一次的数字
字号:小|大
2019-10-09 FW.5VV.CN范文网
程序员面试题精选100题(63)-数组中三个只出现一次的数字
在将这个题目之前,先讲讲一个数组中有两个数字出现一次,其他的都出现两次的情况。具体题目如下:
题目:一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
分析:首先考虑这个问题的一个简单版本:一个数组里除了一个数字之外,其他的数字都出现了两次。请写程序找出这个只出现一次的数字。这个题目的突破口在哪里?题目为什么要强调有一个数字出现一次,其他的出现两次?想到了异或运算的性质:任何一个数字异或它自己都等于0。也就是说,如果从头到尾依次异或数组中的每一个数字,那么最终的结果刚好是那个只出现一次的数字,因为那些出现两次的数字全部在异或中抵消掉了。
有了上面简单问题的解决方案之后,回到原始的问题。如果能够把原数组分为两个子数组,在每个子数组中,包含一个只出现一次的数字,而其他数字都出现两次。如果能够这样拆分原数组,按照前面的办法就是分别求出这两个只出现一次的数字了。
还是从头到尾依次异或数组中的每一个数字,那么最终得到的结果就是两个只出现一次的数字的异或结果。因为其他数字都出现了两次,在异或中全部抵消掉了。由于这两个数字肯定不一样,那么这个异或结果肯定不为0,也就是说在这个结果数字的二进制表示中至少就有一位为1。在结果数字中找到第一个为1的位的位置,记为第N位。现在以第N位是不是1为标准把原数组中的数字分成两个子数组,第一个子数组中每个数字的第N位都为1,而第二个子数组的每个数字的第N位都为0。
现在已经把原数组分成了两个子数组,每个子数组都包含一个只出现一次的数字,而其他数字都出现了两次。因此到此为止,所有的问题都已经解决。
测试代码如下:
#include<iostream>
using namespace std;
unsigned int FindFirstBitIs1(int num)
{
int indexBit=0;
while(((num&1)==0) &&(indexBit<32))
{
num=num>>1;
++indexBit;
}
return indexBit;
}
bool IsBit1(int num,unsigned int indexBit)
{
num=num>>indexBit;
return (num&1);
}
void FindNumsAppearOnce(int data[],int length,int &num1,int &num2)
{
if(length<2)
return;
int resultExclusiveOR=0;
for(int i=0;i<length;i++)
resultExclusiveOR^=data[i];
unsigned int indexOf1=FindFirstBitIs1(resultExclusiveOR);
num1=num2=0;
for(int j=0;j<length;j++)
{
if(IsBit1(data[j],indexOf1))
num1^=data[j];
else
num2^=data[j];
}
}
int main()
{
int a[8]={2,3,6,8,3,2,7,7};
int x,y;
FindNumsAppearOnce(a,8,x,y);
cout<<x<<" "<<y<<endl;
return 0;
}
下面讲解一个数组中出现三个只出现一次的数字,这个题目其实和上面类似。具体的题目与分析如下:
题目:一个整型数组里有三个数字出现了一次,其他的数字都出现了两次。请写程序找出这3个只出现一次的数字。
分析:思路类似于上题,关键是找出第一个来,然后借助上题结论求另外两个。
假设x y z为只出现一次的数,其他出现偶数次。lowbit为某个数从右往左扫描第一次出现1的位置,则x^y、 x^z、 y^z 这三个值的lowbit有一个规律,其中肯定两个是一样的,另外一个是不一样的。令flips为上述三个值的异或,即flips=lowbit(a^b)^lowbit(a^c)^lowbit(b^c)。因此,可以利用此条件获得某个x(或者y,或者z),循环判断的条件是a[i]^xors的lowbit==flips(其中xors为所有数的异或值)
解释:a[i]^xors即可划分为两组,一组是lowbit与flips不同,一组是lowbit与flips相同。这样就能找到某个x,y,z,找出后,将其与数组最后一个值交换,在利用上题思路,在前面n-1个数中找出剩余两个。
具体的代码实现:
#include<iostream>
using namespace std;
int lowbit(int x)
{
return x&~(x-1);
}
void Find2(int seq[],int n,int& a,int& b)
{
int xors=0;
for(int i=0;i<n;i++)
xors^=seq[i];
int diff=lowbit(xors);
for(int j=0;j<n;j++)
{
if(diff & seq[j]) //与运算,表示数组中与异或结果位为1的位数相同
a^=seq[j];
else
b^=seq[j];
}
}
//三个数两两的异或后lowbit有两个相同,一个不同,可以分为两组
void Find3(int seq[],int n,int &a,int &b,int &c)
{
int i,xors=0;
for(i=0;i<n;i++)
xors^=seq[i];
int flips=0;
for(i=0;i<n;i++) //因为出现偶数次的seq[i]和xors的异或,异或结果不改变
flips^=lowbit(xors^seq[i]); //表示的是:flips=lowbit(a^b)^lowbit(a^c)^lowbit(b^c)
//三个数两两异或后lowbit有两个相同,一个不同,可以分为两组
//所以flips的值为:lowbit(a^b)或lowbit(a^c)或lowbit(b^c)
//得到三个数中的一个
a=0;
for(i=0;i<n;i++)
{
if(lowbit(seq[i]^xors)==flips) //找到三个数两两异或后的lowbit与另外两个lowbit不同的那个数。
a^=seq[i];
}
//找出后,与数组中最后一个值交换,利用Find2找出剩余的两个。
for(i=0;i<n;i++)
{
if(a==seq[i])
{
int temp=seq[i];
seq[i]=seq[n-1];
seq[n-1]=temp;
}
}
//利用Find2,找出剩余的两个
Find2(seq,n-1,b,c);
}
int main(void)
{
int seq[]={2,3,3,2,4,6,4,10,9,8,8};
int a=0,b=0,c=0;
Find3(seq,11,a,b,c);
cout<<a<<endl;
cout<<b<<endl;
cout<<c<<endl;
return 0;
}
在将这个题目之前,先讲讲一个数组中有两个数字出现一次,其他的都出现两次的情况。具体题目如下:
题目:一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。要求时间复杂度是O(n),空间复杂度是O(1)。
分析:首先考虑这个问题的一个简单版本:一个数组里除了一个数字之外,其他的数字都出现了两次。请写程序找出这个只出现一次的数字。这个题目的突破口在哪里?题目为什么要强调有一个数字出现一次,其他的出现两次?想到了异或运算的性质:任何一个数字异或它自己都等于0。也就是说,如果从头到尾依次异或数组中的每一个数字,那么最终的结果刚好是那个只出现一次的数字,因为那些出现两次的数字全部在异或中抵消掉了。
有了上面简单问题的解决方案之后,回到原始的问题。如果能够把原数组分为两个子数组,在每个子数组中,包含一个只出现一次的数字,而其他数字都出现两次。如果能够这样拆分原数组,按照前面的办法就是分别求出这两个只出现一次的数字了。
还是从头到尾依次异或数组中的每一个数字,那么最终得到的结果就是两个只出现一次的数字的异或结果。因为其他数字都出现了两次,在异或中全部抵消掉了。由于这两个数字肯定不一样,那么这个异或结果肯定不为0,也就是说在这个结果数字的二进制表示中至少就有一位为1。在结果数字中找到第一个为1的位的位置,记为第N位。现在以第N位是不是1为标准把原数组中的数字分成两个子数组,第一个子数组中每个数字的第N位都为1,而第二个子数组的每个数字的第N位都为0。
现在已经把原数组分成了两个子数组,每个子数组都包含一个只出现一次的数字,而其他数字都出现了两次。因此到此为止,所有的问题都已经解决。
测试代码如下:
#include<iostream>
using namespace std;
unsigned int FindFirstBitIs1(int num)
{
int indexBit=0;
while(((num&1)==0) &&(indexBit<32))
{
num=num>>1;
++indexBit;
}
return indexBit;
}
bool IsBit1(int num,unsigned int indexBit)
{
num=num>>indexBit;
return (num&1);
}
void FindNumsAppearOnce(int data[],int length,int &num1,int &num2)
{
if(length<2)
return;
int resultExclusiveOR=0;
for(int i=0;i<length;i++)
resultExclusiveOR^=data[i];
unsigned int indexOf1=FindFirstBitIs1(resultExclusiveOR);
num1=num2=0;
for(int j=0;j<length;j++)
{
if(IsBit1(data[j],indexOf1))
num1^=data[j];
else
num2^=data[j];
}
}
int main()
{
int a[8]={2,3,6,8,3,2,7,7};
int x,y;
FindNumsAppearOnce(a,8,x,y);
cout<<x<<" "<<y<<endl;
return 0;
}
下面讲解一个数组中出现三个只出现一次的数字,这个题目其实和上面类似。具体的题目与分析如下:
题目:一个整型数组里有三个数字出现了一次,其他的数字都出现了两次。请写程序找出这3个只出现一次的数字。
分析:思路类似于上题,关键是找出第一个来,然后借助上题结论求另外两个。
假设x y z为只出现一次的数,其他出现偶数次。lowbit为某个数从右往左扫描第一次出现1的位置,则x^y、 x^z、 y^z 这三个值的lowbit有一个规律,其中肯定两个是一样的,另外一个是不一样的。令flips为上述三个值的异或,即flips=lowbit(a^b)^lowbit(a^c)^lowbit(b^c)。因此,可以利用此条件获得某个x(或者y,或者z),循环判断的条件是a[i]^xors的lowbit==flips(其中xors为所有数的异或值)
解释:a[i]^xors即可划分为两组,一组是lowbit与flips不同,一组是lowbit与flips相同。这样就能找到某个x,y,z,找出后,将其与数组最后一个值交换,在利用上题思路,在前面n-1个数中找出剩余两个。
具体的代码实现:
#include<iostream>
using namespace std;
int lowbit(int x)
{
return x&~(x-1);
}
void Find2(int seq[],int n,int& a,int& b)
{
int xors=0;
for(int i=0;i<n;i++)
xors^=seq[i];
int diff=lowbit(xors);
for(int j=0;j<n;j++)
{
if(diff & seq[j]) //与运算,表示数组中与异或结果位为1的位数相同
a^=seq[j];
else
b^=seq[j];
}
}
//三个数两两的异或后lowbit有两个相同,一个不同,可以分为两组
void Find3(int seq[],int n,int &a,int &b,int &c)
{
int i,xors=0;
for(i=0;i<n;i++)
xors^=seq[i];
int flips=0;
for(i=0;i<n;i++) //因为出现偶数次的seq[i]和xors的异或,异或结果不改变
flips^=lowbit(xors^seq[i]); //表示的是:flips=lowbit(a^b)^lowbit(a^c)^lowbit(b^c)
//三个数两两异或后lowbit有两个相同,一个不同,可以分为两组
//所以flips的值为:lowbit(a^b)或lowbit(a^c)或lowbit(b^c)
//得到三个数中的一个
a=0;
for(i=0;i<n;i++)
{
if(lowbit(seq[i]^xors)==flips) //找到三个数两两异或后的lowbit与另外两个lowbit不同的那个数。
a^=seq[i];
}
//找出后,与数组中最后一个值交换,利用Find2找出剩余的两个。
for(i=0;i<n;i++)
{
if(a==seq[i])
{
int temp=seq[i];
seq[i]=seq[n-1];
seq[n-1]=temp;
}
}
//利用Find2,找出剩余的两个
Find2(seq,n-1,b,c);
}
int main(void)
{
int seq[]={2,3,3,2,4,6,4,10,9,8,8};
int a=0,b=0,c=0;
Find3(seq,11,a,b,c);
cout<<a<<endl;
cout<<b<<endl;
cout<<c<<endl;
return 0;
}
- 上一篇:程序员面试题精选100题(62)-C/C++/C#面试题(5)
- 下一篇:没有了
相关文章
- 程序员面试题精选100题(03)-第一个只出现一次的字符[算法]
- 程序员面试题精选100题(21)-左旋转字符串[算法]
- 程序员面试题精选100题(24)-栈的push、pop序列[数据结构]
- 程序员面试题精选100题(26)-和为n连续正数序列[算法]
- 程序员面试题精选100题(32)-不能被继承的类[C/C++/C#]
- 程序员面试题精选100题(46)-对称子字符串的最大长度[算法]
- 程序员面试题精选100题(04)-把字符串转换成整数
- 程序员面试题精选100题(10)-排序数组中和为给定值的两个数字[算法]
- 程序员面试题精选100题(16)-O(logn)求Fibonacci数列[算法]
- 程序员面试题精选100题(17)-把字符串转换成整数[算法]
热门推荐
- 程序员面试题精选100题(03)-第一个只出现一次的字符[算法]
- 程序员面试题精选100题(21)-左旋转字符串[算法]
- 程序员面试题精选100题(24)-栈的push、pop序列[数据结构]
- 程序员面试题精选100题(26)-和为n连续正数序列[算法]
- 程序员面试题精选100题(32)-不能被继承的类[C/C++/C#]
- 程序员面试题精选100题(46)-对称子字符串的最大长度[算法]
- 程序员面试题精选100题(04)-把字符串转换成整数
- 程序员面试题精选100题(10)-排序数组中和为给定值的两个数字[算法]
- 程序员面试题精选100题(16)-O(logn)求Fibonacci数列[算法]
- 程序员面试题精选100题(17)-把字符串转换成整数[算法]