我们来看看阿里面试中的三道题目,都跟唯一数有关,层层递进,非常有趣。
小学生也能读懂题目的意思,但并不好做。话不多说,早晨起来,手绘一幅。

涛哥手绘
There is only you in my heart
题目一(简单)
在n个整数中,仅有1个整数出现1次,其余的整数都出现了偶数次,求这个仅出现1次的整数。要求时空复杂度尽可能低。
分析1:用记录次数的方法,空间复杂度不过关,无法通过阿里的面试。
分析2:用排序的方法,时间复杂度不过关,自然无法通过阿里的面试。
分析3:采用异或的方法,直接得出结果,可以通过阿里的面试,程序:
- #include <iostream>
- using namespace std;
-  
- int main()
- {
-   int a[] = {3, 4, 6, 5, 5, 6, 4};
-   int n = sizeof(a) / sizeof(a[0]);
-   int result = 0;
-   for(int i = 0; i < n; i++)
-   {
-     result ^= a[i];
-   }
-  
-   cout << result << endl; // 3
-   return 0;
- }
上述程序的结果是3,其背后的逻辑是:
-  
- result = 3 ^ 4 ^ 6 ^ 5 ^ 5 ^ 6 ^ 4
-        = 3 ^ (4 ^ 4) ^ (5 ^ 5) ^ (6 ^ 6)
-        = 3 ^ 0 ^ 0 ^ 0
-        = 3
题目之二(不难)
在n个整数中,有1个奇数仅出现一次,有1个偶数仅出现一次,其余的整数都出现了偶数次,求奇数和偶数的值。要求时空复杂度尽可能低。
分析:计数法和排序法都无法通过阿里面试,考虑用异或法。设所有整数为:3,8,4,6,5,5,6,4,把所有整数分为奇数派和偶数派,如下:
| 奇数派 | 偶数派 | 
| 3,5,5 | 8,4,6,6,4 | 
很显然,可以分别对奇数派和偶数派进行求解,程序如下:
-  
- #include <iostream>
- using namespace std;
-  
- int main()
- {
-   int a[] = {3, 8, 4, 6, 5, 5, 6, 4};
-   int n = sizeof(a) / sizeof(a[0]);
-   int result1 = 0;
-   int result2 = 0;
-   for(int i = 0; i < n; i++)
-   {
-     if (a[i] % 2 != 0) // 奇数
-     {
-       result1 ^= a[i];
-     }
-     else // 偶数
-     {
-       result2 ^= a[i];
-     }
-  
-   }
-  
-   cout << result1 << endl;  // 3
-   cout << result2 << endl;  // 8
-   return 0;
- }
经自测,程序结果正确。结果为3和8.
题目之三(困难)
在n个整数中,有2个不同的整数分别出现1次,其余的整数都出现了偶数次,求仅出现1次的2个整数。要求时空复杂度尽可能低。
分析:这道题的难度更大了。计数法和排序法都无法通过阿里面试,不予考虑。以3,7,4,6,2,2,4,6为例,该怎样把3和7区分开来呢?通过奇偶肯定不行。我们来看下3和7的二进制:
| 3 | 7 | |
| 二进制 | 0011 | 0111 | 
可以看到,在3和7的二进制中,倒数第三位不同。而且,需要说明的是,由于3和7不同,所以它们的二进制数中,必然存在不同的位。根据这个特征,可以把原来的整数进行划分,分别定义为蓝系和红系,如下:
| 整数 | 二进制 | 类别 | 
| 3 | 0011 | 蓝系 | 
| 7 | 0111 | 红系 | 
| 4 | 0100 | 红系 | 
| 6 | 0110 | 红系 | 
| 2 | 0010 | 蓝系 | 
| 2 | 0010 | 蓝系 | 
| 4 | 0100 | 红系 | 
| 6 | 0110 | 红系 | 
然后,分别对蓝系和红系进行异或,就求出了3和7,程序如下:
- #include <iostream>
- using namespace std;
-  
- int getOXR(int a[], int n)
- {
-     int result = 0;
-     for(int i = 0; i < n; i++)
-     {
-         result ^= a[i];
-     } 
-  
-     return result;
- }
-  
- int getMask(int r)
- {
-     int mask = 1;
-     while((mask & r) == 0) 
-     {
-         mask <<= 1;
-     } 
-  
-     return mask;
- }
-  
- void getResult(int a[], int n, int &A, int &B)
- {
-     int result = getOXR(a, n);
-     int mask = getMask(result);
-  
-     for (int i = 0; i < n; i++)
-     {
-         if (mask & a[i])
-         {
-             A ^= a[i];
-         }
-     }
-  
-     B = A ^ result;
- }    
-  
- int main()
- {
-     int a[] = {3, 7, 4, 6, 2, 2, 4, 6};
-     int n = sizeof(a) / sizeof(a[0]);
-     int A = 0, B = 0;
-     getResult(a, n, A, B);
-     cout << A << endl;  // 7
-     cout << B << endl;  // 3
-  
-     return 0;
- }
经自测,程序结果正确。结果为7和3.
异或逻辑简介
异或,是一种很巧妙的思维,在实际开发中,也会偶尔用到异或,而且,这类按位运算是非常快的。
我们来看如下电路图的动图,它实现的是一位二进制的异或逻辑,其实就是不带进位的二进制加法。

可见:当D1和D2都亮或者都熄灭时,D3熄灭; 而当D1和D2有且仅有一个亮时,D3亮。具体逻辑如下:
| D1 | 操作 | D2 | D3 | 
| 0 | 异或 | 0 | 0 | 
| 0 | 异或 | 1 | 1 | 
| 1 | 异或 | 0 | 1 | 
| 1 | 异或 | 1 | 0 | 

 
                

















