查看原文
其他

腾讯终面:孤单的QQ号码怎么找?

脚本之家 2022-09-23

The following article is from 爱码有道 Author 点击关注👉👉

 关注
“脚本之家
”,与百万开发者在一起

来源:爱码有道(ID:aimayoudao)
如若转载请联系原公众号

今天,我们来看看腾讯终面的两道算法题目,看似简单,但要现场全部做对,也不容易,直接决定了能否拿到腾讯offer, 一起来看看吧。


题目一

有n个QQ号码,除1个孤单的QQ号码外,其余的QQ号码都是成双成对的,求这个孤单的QQ号码,要求:时间复杂度为O(n), 空间复杂度为O(1).

如果你不懂这个题目的具体意思,那我来画个动图,相信你就会明白了。看动图:两个520是一对的,两个216也是一对的,而剩下的111是孤单的。

那么,怎么求出111这个孤单的QQ号码呢?且听我慢慢道来。

方法1: 直接记录

先来介绍最直接的方法:遍历每个元素,记录每个号码出现的次数,比如:

hash_map[520] = 2hash_map[216] = 2hash_map[111] = 1
于是乎,可以轻易地看到111出现的次数是1,所以,孤单的QQ号就是111.

然而,这种方法无法通过腾讯的面试,因为空间复杂度太大,不符合要求。


方法2: 排序求解

于是,想到了计算机中最常用的方法:排序法。显而易见,排序后结果为:

显然,排序后就更清楚了,再遍历一遍,就容易知道孤单的QQ号是111.
一提到排序算法,很显然知道,时空复杂度不达标,无法通过腾讯的面试


方法3: 异或求解

遇到这种题目,需要有一定的敏感度,其实就是需要刷题。直接异或搞起:

result = 520 ^ 216 ^ 216 ^ 520 ^ 111 = (520 ^ 520) ^ (216 ^ 216) ^ 111       = 0 ^ 0 ^ 111       = 111
根据异或算法的交换律和结合律,很容易破解。方法挺巧妙的,也挺实用。

显然,时空复杂度符合要求,可以通过腾讯的面试。第一题万事大吉了哦。

关于异或,我们需要知道,相同两数的异或值为0,而0和x的异或值仍为x.

接下来,我们用实际的程序验证一下,有兴趣的朋友也可以亲自动手试下:

#include <iostream>using namespace std;
int main(){ unsigned int a[] = {520, 216, 216, 520, 111}; unsigned int n = sizeof(a) / sizeof(a[0]); unsigned int result = 0; for(int i = 0; i < n; i++) { result ^= a[i]; } cout << result << endl; // 结果 111}

题目二

有n个QQ号码,除2个孤单的QQ号码外,其余的QQ号码都是成双成对的,求这2个孤单的QQ号码,要求:时间复杂度为O(n), 空间复杂度为O(1).

‍‍‍‍‍‍‍‍‍如果你不懂这个具体题目,那我来画个动图,相信你就会明白。看动图:两个520是一对的,两个216也是一对的,剩下的734和111是孤单的。

‍‍‍‍‍‍‍‍‍那么,怎么求出734和111这两个QQ号码呢?我相信,你肯定已经放弃了计数法和排序法,这就对了。异或算法搞起来!

1. 探索异或

result = 520 ^ 216 ^ 734 ^ 216 ^ 520 ^ 111 = (520 ^ 520) ^ (216 ^ 216) ^ 734 ^ 111       = 0 ^ 0 ^ 734 ^ 111       = 734 ^ 111       = 689

糟糕了,貌似这次用异或算法不灵了。因为题目变难了,题目中有2个孤单数,上述的689并不是正解。

迷茫之际,必须探索新的思路,才有新的出路。我们先来看看二进制异或值的计算方法,如下表格所示:

x0
0
1
1
y
0
1
0
1
异或结果
0
1
1
0
那么,我们来看看734和111的异或过程,其中,a为734,b为111,直接用二进制异或,如下表格所示:

十进制
二进制
a
734
0000 0010 1101 1110
b
111
0000 0000 0110 1111
异或结果
result0000 0010 1011 0001

可以看到,a和b的异或二进制值是:0000 0010 1011 0001,也就是十进制的689,所以result = 689.

由于734和111不同,故异或值不可能是0,因此,对于它们异或值689的二进制值而言,必然存在一个1.

可以看到,689的最后一位是1,因此可知:734和111的二进制的最后一位,必然是一个为0,另一个为1.

2. 探索分组

接着,我们可以根据二进制中最后一位是否为1,把原来的数据分为两类。先来看看原来数据的二进制值吧:

十进制
二进制
520
0000 0010 0000 1000
216
0000 0000 1101 1000
734
0000 0010 1101 1110
216
0000 0000 1101 1000
520
0000 0010 0000 1000
111
0000 0000 0110 1111

很显然,通过上述规则的划分,我们把原来的数据分成了两组,分别是上面表格中的红色部分和蓝色部分。

接下来,分别对这两组求异或,就可以得到两个最后的值,也就是我们苦苦追求的两个孤单数,具体如下。

第一组:

result1 = 520 ^ 216 ^ 734 ^ 216 ^ 520 = (520 ^ 520) ^ (216 ^ 216) ^ 734        = 734
第二组:

result2 = 111 ^ 0  = 111

3. 步骤总结

接下来,我们来总结一下算法步骤:

  • Step1: 计算出原数组的所有数字的异或值result, result的值必然不为0,且一定是两个孤单数的异或值。

  • Step2: result的二进制中,必然存在第k位的值为1. 以第k位是否为1来作为判断标准,对原数组进行划分。

  • Step3: 将原数组二进制第k位为0的数,认为是第一个集合,并计算出它们的异或值,形成第一个孤单数。

  • Step4: 将原数组二进制第k位为1的数,认为是第二个集合,并计算出它们的异或值,形成第二个孤单数。

该算法非常巧妙,时间复杂度为O(n), 空间复杂度为O(1), 完全符合题目的要求,可以通过腾讯的面试

异或算法很巧妙,在面试中也会经常用到,需要引起重视。我们也会一步一个脚印,争取每篇文章讲清讲透一件事,也希望大家阅读后有所收获,心情愉快。

  推荐阅读:

终于!我找到程序员爱穿卫衣的原因了

面试官:说下你对方法区演变过程和内部结构的理解

《面试八股文》之 JVM 20卷

面试官:谈谈 Exception 和 Error 的区别及注意事项

面试官又整新活,居然问我for循环用i++和++i哪个效率高?

神器 Typora 开始收费!到底更新了啥?

每日打卡赢积分兑换书籍入口

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存