头条 面试经历

一面:

  2019年5月14日晚上8点整,我在网鱼网咖(深大风蝶店)进行今日头条一面的视频面试。总体来说面试结果不太好,估计是挂了。但还是大概记录一下整个面试过程吧。

  首先确认了我是腾讯的,主要使用C++,当前职级是T2-1后,就让我做自我介绍,然后开始问问题。

1. c++ 的 stl 有用过吗(有)map 的底层数据结构是什么(红黑树)红黄树的操作复杂度是多少(logn)logn 复杂度的数据结构还有哪些(AVL 等各种平衡二叉树、跳表等)为什么用红黑树而不用这些数据结构(我说是因为 AVL 需要高度平衡,维护平衡因子太过严谨,红黑树会更灵活一些……之类,自己都说服不了 o(╯□╰)o )

2. 有两个 set,容量分别是 M 和 N,其中 M > N,求这两个 set 的交集,是应该遍历小的在大的里 find 还是应该遍历大的在小的里 find 呢(我说遍历小的在大的里面 find )为什么(因为 MlogN > NlogM,我原本以为是 MlogMlogN > NlogNlogM => M > N)给出数学证明(不等式变换一下为:M / logM > N / logN,那么只需要证明 x / logx 为单调递增函数就行了。我当时的答案是 M / N > logM / logN,因为 y = x 比 y = logx 增长要快,所以有这个不等式,其实这样说也是不太严谨,证明不了的~)

  从这道题可以看出数学还是非常有用的。

3. linux 进程和线程有什么区别(我说了进程是资源分配的基本单位,线程是调度的基本单位,fork 和 clone,内存是否复制等等)线程切换为什么比进程切换开销要小?切换进程时操作系统做了哪些事?(为进程保存上下文,然后进程进入就绪状态,等待 os 调度再次进入运行状态)什么情况下会发生进程切换(我说当进程发生 I/O 时,中断时,或者时间片到了后,但面试官不是很满意)进程切换时哪些会被 os 换出去?页表会换出去吗?(这里我就真不知道怎么回答了~)

4. mysql innodb myisam 索引所用数据结构(b+树)为什么采用 b+树?(我说了 b+树比 b树的几点优势…)

  当事务隔离级别为 rr (可重复读) 时,select * from table where id = xx for update 哪些东西会被锁上?其中 id 是个唯一索引(我说该行会被锁)然后面试官问如果 id 是个普通索引呢(我说所有 id = xx 的行都会被锁上,但面试官不满意,应该不是正确答案)

5. redis 常用数据结构,zset 底层实现(跳表 + hash,数据共用,指针相连)为什么用跳表而不用其它复杂度相同的数据结构?(我说因为 redis 作者感觉跳表比其它数据结构更好维护之类的~)redis 有哪几种持久化方式(rdb, aof)说一下 rdb 的持久化过程(我啪啦啪啦地说了一通)加入子进程备份过程中,父进程收到客户端的一个 set key value 的命令,那么子进程最终备份的 rdb 文件里有没有包含这个 key 或者这个 key 是否等于 value?(我说不会更改,因为父子进程的内存是独立的,子进程感知不到父进程内存的变化)

6. 编程题

(1)随机洗牌,输入54个int,输出随机洗牌后的54个int,我问有复杂度要求吗?面试官说你先写。

  我为所有牌生成一个随机数,用随机数来排序,再输出排序后的牌面值。面试官提问复杂度是多少,我说复杂度主要在排序,如果用快排的话是 nlogn,可以使用基数排序或者计数排序优化到 O(n) 级别。然后他说随机数一定随机吗,所有可能出现的组合时多少个?(54的阶乘个)你怎么保证 54!个组合的出现概率是均等的?我说只要随机数足够随机就能保证均等的……

(2)strlen 高效实现

  我以为只想考察一些边界的检测,就写了一个很常规的,他说能否把速度提升 4 倍或者 8 倍,我优化了一下,结果他哈哈大笑,好像是笑我傻。然后告诉我……

  第二天在网上搜了下发现和面试官说得差不多,通过 int* / long* 型指针进行 4 个字节或 8 个字节的移动,每次移动时可以通过位运算在 O(1) 时间内快速判断 这 4/8 个字节中是否存在位全 0 的字节(即 '\0'),还有个要注意的地方,就是一开始时要先进行字节对齐,需要是 4/8 的倍数才能开始移动。

  8点40分时面试结束,整个面试过程大概40分钟。

二面:

  2019年5月17日下午5点整,我再次在网鱼网咖(深大风蝶店)进行今日头条二面的视频面试。面试整体感觉还不错,比一面感觉表现得要稍好一些,但一道很关键的算法题还是没有做出来。

  首先让我自我介绍,我基本照着一面的自我介绍说了下,然后面试官让我详细说其中一个项目,我于是把运营活动系统详细地介绍了下,所有的优化和重构等等。结果给自己挖坑了,前面很有把握的优化点说得很详细,核销服务的限流放到最后来说,面试官可能不记得我前面说的是什么了,在我说完后就抓着限流这方面让我详细介绍下,于是我把自己想的还没有实现的方案说了下,利用 redis incr 原子性增减的 key 作为请求计数器实现限流,离线程序定时删除过期 key。然后面试官抓着这个点深挖下去,问会有什么不足,假如离线程序删漏了 key 该怎么办。我说可能存在单点瓶颈,可以使用多个 redis 来发号分摊压力等等,key 的数量增长是可控的,一天最多86400 个 key…… 问有没有其它方案,我说可以使用令牌桶之类的算法,不过没有仔细研究过,然后他让我介绍令牌桶,刚好我前几天看了下令牌桶相关的资料,虽然了解得不深,但还是大概知道是个什么东西,便根据记忆说了下。面试官又问我令牌桶有什么不足,接着又回到我的方案上面,让我想下有没有要补充的,或者可能会存在什么问题不,我想了一下,说假如 redis 挂了,可以降级使用共享内存来实现发号,当然限流的上限要均摊到各个机器上面。总之就限流这个点说了很多,后面想起来时再补充。

  接下来就是编程题,第一道是给一个无序数组,数组中有一个 0 (后面才知道可能有多个),把大于 0 的放到它左边,小于 0 的放到它右边,实现这样的一个函数。我当时第一反应就是快排的交换流程,问他是先说思路还是先写代码,他说直接写代码 (后面才知道是要能 run 的)。我凭着记忆去把快排的交换流程在屏幕上写,因为第一次在牛客网上面试编程,不熟悉输入输出的规则,像写 acm 的题一样写了 while(~scanf) 之类的输入,面试官让我不用写输入,直接把样例贴进代码里运行输出即可。我写得差不多后就点击运行,结果竟然超时了,应该是快排的交换函数没写好 (毕竟没默写过~),看了大概好一会,找到写错的地方了,改正过来后终于能输出正确的结果了,给面试官看后他却提示我要看题,是把大于 0 的放到左边,小于 0 的放到右边,我弄反了,此刻我觉得自己好像那些背代码的面试者。把函数里大于和小于比较的地方交换了后,终于能输出面试官想要的结果了,他暂时算我通过了。

  紧着着时第二题,LCA,最近公共祖先,还是求一颗二叉树上一系列结点的 LCA,他的题目还没说完,我就觉得凉了,LCA 之前看了一下,还没有看懂算法,更不要说写代码了。我想了挺久,和他说了一下思路,先用 Tarjan 算法预处理出所有结点的信息(这里其实我不知道是什么信息),再依次求出 vector<List*> 中所有结点的最近公共祖先,总体复杂度是 O(n+k),其中 n 是总结点数,k 是要求的结点个数。他说那你能不能实现一下用代码写出来,我说这个算法我忘了(其实是根本不会~),写不出来 o(╯□╰)o  然后他问空间复杂度是多少,我说是 O(n) 的,因为要记录所有结点的信息,他说有没有常数空间的算法,我想了一下,想不出来,这道题就到此为止了。

  之后是系统设计题。实现一个简易的微博,需要支持以下功能,可以关注别人,每个人可以发微博,首页可以查看关注过的所有人发的微博,其中未读的排在前面、已读的在后面,然后再根据时间从新到旧排序。我在纸上画了一下,大致说了一下自己随便想出来的草案,先用一个表记录用户和他的所有粉丝,一个粉丝一条记录,每次用户发微博时,推送他的微博到所有粉丝的表去(这个表是个内容表,记录用户接收的微博信息,有已读/未读标记字段等),可以加个缓存记录需要展示在首页的排在前面的微博,然后缓存和 DB 用个离线程序做一致性保证等等,面试官说你这个是一个写扩散的方式,万一有个大 V 他的粉丝数量非常多,那你推送时需要写表的数量岂不是很多,我想了一下确实是这样子,然后拍脑袋说可以把用户分为活跃用户和非活跃用户,活跃用户用推送的方案,非活跃用户看时再去拉取他关注的人的微博(我的思想其实就是冷热数据分离,懒加载,数据需要用到时再去获取),面试官让我仔细说一下实现方案,问如果一个非活跃用户关注了 5000 个人(参考新浪微博的关注上限是 5000 个),那么他打开首页去拉取数据时该怎么拉取,我说从 DB 里记录里微博内容的表(字段大概有 uid、关注着uid、微博内容)里分页查,where uid = '他自己' order by time desc……,他说这样会不会有性能问题,我说用 uid + time 建一个索引那么还是很快的,DB 还是能撑起不少的量的,当支撑不了时再引入缓存,然后做数据一致性保证等,之后他没再问了。

  面试官让我回到刚刚的第一道编程题,给了另一组含有两个 0 的数据,让我用自己刚刚写的哪个程序去跑,我运行输出结果,问他对不对,他一开始以为不对但再看了一下确认是没问题的,然后说我这个函数需要先确定 0 的位置才能作后续的交换操作,而确定 0 的位置需要先遍历一遍,这样子就是 O(2 * n) 的复杂度了,有没有可能实现 O(n) 的复杂度,我说用额外空间记录遍历查找 0 时左边比它小的数,交换操作时不用左边再走一遍?他不是很满意,问能否不用额外空间而实现 O(n) 呢?我想了一下,突然发现不用固定 0 的位置,直接首尾两个指针,左边的遍历到负数,右边的遍历到正数时交换就行了,直到它们相遇为止,这个应该是面试官想要的最终答案了,他不再问下去,结束这道题的提问了。

  可能这道编程题得到他想要的结果后觉得差不多了,他便随意地抛出最后一个问题,linux 虚拟内存的作用?我早上和前一天刚刚在看这个,便照着背出来了。虚拟内存可以实现进程的隔离,由操作系统转换成实际的物理地址,实际的内存操作对应用程序来说是透明的,进程可以不用关心别的进程所使用的内存等等,他问我还有没有要补充的,我实在想不出来了,忍不住反过来问他,还有哪些方面呢?他说没关系,只是想看看我了解的各方面等等。

  最后让我说一个印象深刻的解决问题的经历,我想了一下把之前长连接短连接相关的问题稍微润色一下说出来,curl 库相关的代码改为长连接后耗时没有降低,然后发现是 apache 长连接的配置没有开启,长连接需要客户端和服务端都支持才能正常建立起来的,第二天早上让运维调整后耗时降低,恢复正常。他说那处理得还是挺快的,才一天时间,问我是怎么定位到问题出现的地方的,我随便说了几句因为耗时高的地方很可能出现在网络 I/O 的地方,内存操作的业务逻辑都是非常快的等等。

  可能赶着去吃饭或者回家,他准备结束面试了,便问我有什么想问的,我像一面一样问他我有没有需要改进或者提升的地方,他笑着说这个和面试结果相关的不方便说,要不你还是换个问题吧,我便问了两个我其实不是很关心的但又不敏感的问题:头条每个人负责的业务是不是比较精细化,像 BAT 那样呢?他说了不少,说没有QA,都是开发自测等等,然后我又问他你们的技术是不是都是挺新的,因为我现在所在的部门技术比较老旧,他说他们用的是 golang,框架是xxx,内部也有自研的 k-v 数据库等等,还说对于语言或者框架其实不太看中,所以面试时也没问我语言相关的东西,只是考察对于系统设计的思想之类的(和我想的一样,这个才是最重要的,所以我这段时间也没复习过 C++ 相关的知识,都是在看算法、数据库、网络、os 等等之类的面试题),他问有没有解答了我的问题,这么客气我不知道该怎么回应,便笑着说可以了,挺好的,然后他说面试就到这里结束吧,我道谢后大家就退出视频界面后结束面试。

  此时是18点17分,整个面试过程大概1个小时17分,总体感觉比一面表现得要好一点,但是 LCA 这道面试题没答出来是硬伤,可能会作为最终面试结果的参考标准,不过我感觉也赚了,至少通过这场面试也有收获,和一面一样。一面收获的是覆盖面比较广的各个知识点,二面主要是算法需要加强,同时每个问题都可以深挖,看你能想到什么程度。

三面:

  5月18日 (周六) 早上11点多的时候,一个新的 HR 打电话过来约三面时间,原本她想约周日的,因为周日他们上班,我不上班,刚刚好,我问她周一可以吗,因为周日没空(其实是想多点时间复习),于是约周一晚上6点的时间进行面试。

  5月20日 (周一) 晚上6点整,我第三次在网鱼网咖(深大风蝶店) 进行今日头条三面的视频面试。面试官后来了解到是总监级别的,看着应该是不写代码的了。自我介绍过后,他让我介绍秒杀系统,接着又问到限流相关的东西,当请求量多时该怎么处理,记得好像是引导我使用 mq 之类的机制,并加入优先级的概念,根据用户点击的时间来进行优先级划分,是考虑到用户手机到达服务器的路径和时间时不确定的,所以需要进行优先级排序处理。我说那是由前端来传递点击的时间吗?这样子会不会不安全?他好像是被我提醒了,然后说在忽略安全的前提下怎么做,我说就在内存里维护一个优先队列吧,根据前端传进来的时间进行排序之类的。

  接着他零零散散地问了我其它的,mysql 事务一致性的实现,我提了一下 undo log 和 redo log 之类的概念,但是底层实现并不了解,好像糊弄过去了。他又问 redis 的数据类型,zset 的底层实现,这个我比较了解,就说了一下,zset 底层是跳表+哈希表,数据时共用的,通过指针相连等等。

  然后面试官诡异地笑了一下,抛出了二面时我没有做出的那道 LCA 的算法题,问我回去后有没有了解过,我说有简单了解过普通的做法,然后他让我写代码,我只好凭着记忆写出来,其实是有 bug 的(毕竟我没有真正写过,只是大体看过算法而已),但我用一组简单的样例让它跑通过给面试官看,面试官果然时很久没写代码的人,好像时看不出我写的是什么,看到我的输出差不多后就觉得 OK 了,算我过关了,

  最后他问我有什么想问的,我和二面一样,问他头条的技术栈、以及觉得我有什么可以改善或提升的,他说我对自己做的项目底层不太了解(我晕~)。说后面 HR 会通知面试结果然后结束视频面试了。

  结束时是18点52分,整个面试过程50分钟左右。走出网咖后19点24分的时候,HR 打来电话约 HR 面的时间,看来是通过三面了,真有效率啊~

HR 面:

  5月21日(周二) 晚上8点时,和 HR 通过一个 zoom 软件进行视频面试,她看出来了我是在网吧里哈哈。自我介绍过后问了一些基本问题,问我目前的薪资以及期望薪资等,我问她能给到的薪资范围是多少,她没有明说,和我说了一下后续的流程,银行流水提交、薪酬审批等等,还问了我什么时候可以入职,当时我并不知道离职流程需要一个月的时间,就随便报了一个时间说一个月后(我以为工作交接两周左右,然后剩下两周搬家以及休息),接着让我给她提供一个非QQ邮箱,因为 offer 邮件这些比较敏感,如果是QQ邮箱被腾讯查出来就不好了。

  5月25日(周六) 收到了 offer 材料(银行流水、工资单等)准备的邮件,可是我27日(周一) 晚上才看到,于是赶紧和 HR 说明情况,第二天早上赶紧准备好银行流水和工资单明细等材料发送过去。

  29日(周三) 晚上 HR 通知补充绩效考核以及年终奖详情。

  5月30日(周四) 晚上8点多,HR 和我电话沟通了 offer 情况,给出了我预期的薪资,我问薪资是否有上调的空间,她说可以试着上调一下,但很接近红线了,超过了需要审批的,还有和我说了年终奖、加班费、房补之类的情况。电话结束后把我和未来的 leader 拉到一个群。

  5月31日(周五) 晚上,新 leader 和我电话聊了一下,介绍了目前我所在部门所负责的业务

  6月2日(周日) 晚上9点,HR 打电话和我再沟通 offer 情况,跟我说稍微调高了一点点,问我能接受不,我说可以接受,然后给我正式发了邮件 offer,毕业之后收到的第一个 offer,内心还是挺激动的。看了一下录用通知函里的内容后,打电话问 HR 几个小问题,然后就回复确认接受。

  6月3日(周一) 收到入职信息填写的邮件,6月19日(周三) 晚上填好提交,其中有一项是入职笔记本电脑的选取,有windows,MacBook和MacBook Pro 13/15寸,我没接触过苹果电脑,在脉脉上问了一下好像大部分人都是选MacBook Pro 15寸,于是自己也选这个吧,毕竟苹果电脑是趋势,大部分开发都是使用它而不是windows。

 

posted @ 2019-05-14 23:43  Newdawn_ALM  阅读(26)  评论(0编辑  收藏  举报