关键词搜索

源码搜索 ×
×

阿里面试:妙解三道唯一数题目

发布2022-01-09浏览1496次

详情内容

我们来看看阿里面试中的三道题目,都跟唯一数有关,层层递进,非常有趣。

小学生也能读懂题目的意思,但并不好做。话不多说,早晨起来,手绘一幅。

图片

涛哥手绘

There is only you in my heart

题目一(简单)

在n个整数中,仅有1个整数出现1次,其余的整数都出现了偶数次,求这个仅出现1次的整数。要求时空复杂度尽可能低。

分析1:用记录次数的方法,空间复杂度不过关,无法通过阿里的面试。

分析2:用排序的方法,时间复杂度不过关,自然无法通过阿里的面试。

分析3:采用异或的方法,直接得出结果,可以通过阿里的面试,程序:

  1. #include <iostream>
  2. using namespace std;
  3. int main()
  4. {
  5. int a[] = {3, 4, 6, 5, 5, 6, 4};
  6. int n = sizeof(a) / sizeof(a[0]);
  7. int result = 0;
  8. for(int i = 0; i < n; i++)
  9. {
  10. result ^= a[i];
  11. }
  12. cout << result << endl; // 3
  13. return 0;
  14. }

 上述程序的结果是3,其背后的逻辑是:

  1. result = 3 ^ 4 ^ 6 ^ 5 ^ 5 ^ 6 ^ 4
  2. = 3 ^ (4 ^ 4) ^ (5 ^ 5) ^ (6 ^ 6)
  3. = 3 ^ 0 ^ 0 ^ 0
  4. = 3

题目之二(不难)

在n个整数中,有1个奇数仅出现一次,有1个偶数仅出现一次,其余的整数都出现了偶数次,求奇数和偶数的值。要求时空复杂度尽可能低。

分析:计数法和排序法都无法通过阿里面试,考虑用异或法。设所有整数为:3,8,4,6,5,5,6,4,把所有整数分为奇数派和偶数派,如下:

奇数派偶数派
3,5,58,4,6,6,4

很显然,可以分别对奇数派和偶数派进行求解,程序如下:

  1. #include <iostream>
  2. using namespace std;
  3. int main()
  4. {
  5. int a[] = {3, 8, 4, 6, 5, 5, 6, 4};
  6. int n = sizeof(a) / sizeof(a[0]);
  7. int result1 = 0;
  8. int result2 = 0;
  9. for(int i = 0; i < n; i++)
  10. {
  11. if (a[i] % 2 != 0) // 奇数
  12. {
  13. result1 ^= a[i];
  14. }
  15. else // 偶数
  16. {
  17. result2 ^= a[i];
  18. }
  19. }
  20. cout << result1 << endl; // 3
  21. cout << result2 << endl; // 8
  22. return 0;
  23. }

经自测,程序结果正确。结果为3和8.

题目之三(困难)

在n个整数中,有2个不同的整数分别出现1次,其余的整数都出现了偶数次,求仅出现1次的2个整数。要求时空复杂度尽可能低。

分析:这道题的难度更大了。计数法和排序法都无法通过阿里面试,不予考虑。以3,7,4,6,2,2,4,6为例,该怎样把3和7区分开来呢?通过奇偶肯定不行。我们来看下3和7的二进制:

37
二进制00110111

可以看到,在3和7的二进制中,倒数第三位不同。而且,需要说明的是,由于3和7不同,所以它们的二进制数中,必然存在不同的位。根据这个特征,可以把原来的整数进行划分,分别定义为蓝系和红系,如下:

整数二进制类别
30011蓝系
70111红系
40100红系
60110红系
20010蓝系
20010蓝系
40100红系
60110红系

然后,分别对蓝系和红系进行异或,就求出了3和7,程序如下:

  1. #include <iostream>
  2. using namespace std;
  3. int getOXR(int a[], int n)
  4. {
  5. int result = 0;
  6. for(int i = 0; i < n; i++)
  7. {
  8. result ^= a[i];
  9. }
  10. return result;
  11. }
  12. int getMask(int r)
  13. {
  14. int mask = 1;
  15. while((mask & r) == 0)
  16. {
  17. mask <<= 1;
  18. }
  19. return mask;
  20. }
  21. void getResult(int a[], int n, int &A, int &B)
  22. {
  23. int result = getOXR(a, n);
  24. int mask = getMask(result);
  25. for (int i = 0; i < n; i++)
  26. {
  27. if (mask & a[i])
  28. {
  29. A ^= a[i];
  30. }
  31. }
  32. B = A ^ result;
  33. }
  34. int main()
  35. {
  36. int a[] = {3, 7, 4, 6, 2, 2, 4, 6};
  37. int n = sizeof(a) / sizeof(a[0]);
  38. int A = 0, B = 0;
  39. getResult(a, n, A, B);
  40. cout << A << endl; // 7
  41. cout << B << endl; // 3
  42. return 0;
  43. }

经自测,程序结果正确。结果为7和3.

异或逻辑简介

异或,是一种很巧妙的思维,在实际开发中,也会偶尔用到异或,而且,这类按位运算是非常快的。

我们来看如下电路图的动图,它实现的是一位二进制的异或逻辑,其实就是不带进位的二进制加法。

图片

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

D1

操作

D2

D3

0

异或

0

0

0

异或

1

1

1

异或

0

1

1

异或

1

0

相关技术文章

点击QQ咨询
开通会员
返回顶部
×
微信扫码支付
微信扫码支付
确定支付下载
请使用微信描二维码支付
×

提示信息

×

选择支付方式

  • 微信支付
  • 支付宝付款
确定支付下载