本文共 2093 字,大约阅读时间需要 6 分钟。
一个整型数组里除了两个数字之外,其他的数字都出现了两次。请写程序找出这两个只出现一次的数字。
题目中没有要求时间复杂度和空间复杂度,如果不考虑这两个因素,确实有挺多的解决办法。牛客网的测试数据量可能不是那么大,很多方法都可以用,基本上思路如下几种:
- 在 O(n2) 的时间复杂度下,对于每一个数组中的元素 i,遍历数组来查找是否有和 i,相等的元素。
- 使用一种排序算法,将数组排序,相同的数字一定是相邻的,这样就可以轻松的找到两个只出现一次的数字,时间复杂度取决于使用的排序算法,一般为 O(nlogn) 。
- 题目中说数组中元素为整型数组,也就是数组中的元素为 int 类型, int 大小一般为32字节,所以可以建立这样一个“记录数组”-
bool hasThisNum[4294967296];
其中数组的大小为 int 类型通常情况下可以表示的数的个数。然后,通过遍历输入的数组对遇到一个元素就在hasThisNum[i]
对应的位置记录一下,最后遍历hasThisNum
找到找到只出现了一次的数字。这样做时间复杂度为 O(n) ,空间复杂度理论上为 O(1) 。由于,,所以这样做既不算完全正确,也不优雅。- 另一种方法是使用一个 map 来代替上一种方法中的数组,实测可以通过。
- 看牛客网该题目的评论里面得分最高的就是使用抑或运算,下面的篇幅记录一下网上找到的使用抑或运算解决这个问题的相关资料。参考自,。
基本思路为,由于抑或的性质(此处定义 ⊕ 为逻辑学中的抑或操作, C 语言中是使用的^
标志)。
基于如上的抑或的性质,可以这样思考这个问题,首先我们考虑这个问题的一个简单版本:一个数组里除了一个数字之外,其他的数字都出现了两次。请写程序找出这个只出现一次的数字。
这个题目的突破口在哪里?题目为什么要强调有一个数字出现一次,其他的出现两次?我们想到了异或运算的性质:任何一个数字异或它自己都等于0 。也就是说,如果我们从头到尾依次异或数组中的每一个数字,那么最终的结果刚好是那个只出现一次的数字,因为那些出现两次的数字全部在异或中抵消掉了。① 有了上面简单问题的解决方案之后,我们回到原始的问题。如果能够把原数组分为两个子数组。在每个子数组中,包含一个只出现一次的数字,而其它数字都出现两次。如果能够这样拆分原数组,按照前面的办法就是分别求出这两个只出现一次的数字了。 我们还是从头到尾依次异或数组中的每一个数字,那么最终得到的结果就是两个只出现一次的数字的异或结果。因为其它数字都出现了两次,在异或中全部抵消掉了。由于这两个数字肯定不一样,那么这个异或结果肯定不为0 ,也就是说在这个结果数字的二进制表示中至少就有一位为1。我们在结果数字中找到第一个为1的位的位置,记为第N位。现在我们以第N位是不是1为标准把原数组中的数字分成两个子数组,第一个子数组中每个数字的第N 位都为1 ,而第二个子数组的每个数字的第N 位都为0 。 现在我们已经把原数组分成了两个子数组,每个子数组都包含一个只出现一次的数字,而其它数字都出现了两次。因此到此为止,所有的问题我们都已经解决。上面这段话,引自。理解了其中的①处,就应该能够完全理解了,下面给出简单的证明。
x1⊕x2⊕x3⊕⋯⊕x2⊕x3⋯⊕xn
=x1⊕x2⊕x2⊕x3⊕x3⊕⋯xn⊕xn =x1⊕0⊕0⊕⋯⊕0 =x1其中 x1 到 xn 为输入的 n 个数,通过上面简单的证明可以看出,如果输入的数组中只有一个数字是出现了一次那么下面这段代码的操作可以利用抑或性质找出该数字。
int temp = 0; //注意temp的初始值为0 for(int i=0;i
由于数组中有两个数字只出现了一次,最终的代码如下
public class 数组中只出现一次的数字 { public void FindNumsAppearOnce(int [] array,int num1[] , int num2[]) { if(array==null ||array.length<2) return ; int temp = 0; for(int i=0;i> 1; ++indexBit; } return indexBit; } public boolean isBit(int num,int indexBit){ num = num >> indexBit; return (num & 1) == 1; }}
1.