“ 本文作者在2019年实习和秋招中面了10多家公司,只吃过一次拒信,拿到的offer中不乏一些竞争非常激烈的外企与国内大厂的ssp,而且开发/算法的offer都有。值得一提的是作者本科并非CS专业,所以本文的内容是具有一定普适性,非常建议即将找实习/工作的同学看一下!”
本文来源:浅梦的粉丝津铭 https://github.com/conanhujinming/tips_for_interview
今年春招和秋招,我在中国进行了多场面试,其目的是找一个暑期实习职位和找秋招的正式工作。这是我的个人心得总结。
我实习和秋招都已经面了数家国内的一线大厂(如阿里等)和数家外企(如Google等),收到过一次拒信。经过一段时间的面试准备与几次面试经历,总结出了一些个人心得,仅供参考。
所有的面试技巧都是建立在一个基础之上:面试者已经具备了相对合格的实力。2018年下半年我在一家创业公司实习,秋招时也面试过一些候选人。在我看来,面试者如果自身基础不扎实、实力不够合格,那看所谓的面经、学习所谓的技巧也意义不大:合格的面试官可以非常轻易地通过一些follow-up问题问出面试者的真实实力。面试技巧和面经固然有意义,但学习技巧和了解面经,「只能帮助有实力的面试者更大程度地发挥出自己的实力」 。「学习没有捷径可走,nothing replaces hard work.」 希望每一位面试者都能尽早明白这个道理。
另一方面,我身边确实有一些这样的同学:他们相当有实力,但是却因为种种原因无法在面试中展现出自己的全部实力。事实上,不同的企业有不同的面试文化,比如Google的面试官希望面试者能成为一个他愿意一起工作的同事,字节跳动的面试官也许希望面试者是一个数学、算法、coding、工程都不错的全面人才,这样的人才更可能成为一个“能解决问题的人”。但是,作为面试候选人,我们其实没必要去针对各家公司的文化对症下药:应对面试应当有一些共通的要点。在我看来,面试最关键的一点在于面试者要意识到这不仅是一场测试,「更是一次需要充满着沟通与交流的谈话,让面试官认为他/她愿意成为你的同事」,希望每一位面试者都能尽早明白这个道理。
除了上面提到的我认为至关重要的两点以外,面试还有一些其他相对通用的面试技巧和要点。我这篇文章旨在总结一些这方面的东西,希望能够帮助到这样的同学。
在面试过程中,面试官常常会给出几道算法问题,需要面试者提供思路或写下代码。在大多数公司的面试中,这一部分的表现都非常重要,而对一些外企来说,这部分的表现是具有决定性的(甚至是唯一重要的表现)。对于这部分的准备,首推LeetCode等网站,这里不再赘述。再提几句话,对于一些重视算法问题的公司如Google, hulu, airbnb, 微软, 头条等,不要抱着可能撞到原题的心态去准备,很难撞到原题的,对于这些公司,你需要做的就是反复练习提升自己的能力,而且由于题目较难,需要有较多的训练量。而另一些不是很重视这类问题的公司像阿里、腾讯什么的,则刷一些常见的题目就很可能撞到原题了,而且难度一般不大。因此,根据target公司的不同,可以有不同的准备方式。下面将列举一些其他在面试中我认为比较关键的点。
这里先给出一个非常简单的问题,下面的关键点将结合这个问题来阐述。该问题为,计算一棵二叉树的高度。简单的实现如下:
int getHeightOfBinaryTree(TreeNode* root) {
if (!root) return 0;
int left_height = getHeightOfBinaryTree(root->left);
int right_height = getHeightOfBinaryTree(root->right);
return max(left_height, right_height) + 1;
}
面试的编程部分往往是白板编程:面试官要么要求在一个类似于Google Doc的地方写代码,要么就是干脆在白纸上写代码。这种情况下coding的体验与平时使用IDE的体验是完全不同的。以Google Doc为例,许多人(比如我)一开始甚至很难写出能编译的代码,更别说一遍写出bug-free的代码了。同时,没了IDE,debug的难度也会大大增加。而在白纸上写代码的难度则还要更进一步。适应白板编程的方法也很简单,只需要足量的练习即可。
问清题目至关重要。如果你对面试官的编程问题理解得不清晰,那你应该立刻问一些能帮助你理解的问题。例如:数据范围是多少?这个数组的大小范围是多少?能不能给个样例?如果输入是这个,那输出应该是什么等等。在上面这个简单的问题中,可以问的一个问题是,二叉树的高度是什么(据我所知,高度的定义并非所有教材都一致)?
许多面试官在面试的时候,会故意先抛出一个模糊的问题。实际上,他们希望面试者能够经过一些询问理解问题。在这个过程中,面试者能够展现出自己对「问题的分析能力」以及「沟通的能力」。前者的重要性参见编程珠玑第一章:明确问题,战役就成功了90%。后者的重要性在于,问清题目的这个交流过程与面试者入职之后与同事讨论问题的形式非常类似。显而易见,一个能够很难沟通的面试者也很难成为一个很好沟通的同事。
如果没有问清题目,那会发生什么事情呢?在最坏情况下,面试者可能会花大量时间去解决一个完全错误的问题,面试结果也可想而知。或者运气好些,碰到了一个比较nice的面试官,给一些提示告诉面试者已经进入误区了,但这样不仅会浪费不少珍贵的面试时间,更会降低面试官对面试者的评价。我在面一家公司的时候,面试官给我出了一个题,这个题听上去比较困难,需要用到动态规划才能实现。我当时想,在面试开始阶段就给出一道比较困难的题,这对我来说也太不友好了!于是我询问了一句”数据的范围是什么呢?“面试官告诉我,数组的范围都是0-10的整数。这样的话,这个问题就变成了一个只需要6行代码就可以解决的贪心问题。如果我没有问清这个问题的话,面试的难度显然大大增加。
确认了题目之后,我认为合理的做法是先和面试官确认函数签名,也即输入是什么参数,输出是什么参数等等。这一步的代价很低,而且相当重要。第一,这可以告诉面试官,你对函数签名的设计相当重视,而这一点在实际应用中很有价值。第二,这可以进一步帮你确认自己理解了题意。一个合理的函数签名可能就类似于LeetCode题目里的函数签名。上面代码中的签名就是一个比较合理的签名。
在自己有了一个思路之后,一定要和面试官确认这个思路是否合理。你可以给面试官解释你的思路为什么合理,面试官可能会和你讨论其中的一些要点。这样做有几点好处。第一,在解释的过程中,你的思路也会变得更加清晰(面试官充当小黄鸭)。第二,这也展现出你对沟通的重视性。第三,可能也是最重要的一点是,如果你的思路不正确,nice的面试官会提示你甚至直接指出错误所在,这样你至少不会在一个错误的思路上耽误太多时间。「切忌有了思路之后,不与面试官交流直接写代码」。尤其需要指出的是,如果你的思路对数据有什么假设,或者需要「修改输入数据」,那一定要和面试官确认这样的做法是合理的。
如果你认为这个问题与某个经典的问题思路一致,或者可以用到某个经典的算法,那么就直接点出来。例如计算二叉树的高度,实际上是一个后序遍历,那么可以直接点出来。
在开始写代码以前或者是写代码的过程中,一定要思考代码的边界条件。最典型的边界条件有:数据是否会溢出?指针是否可能为空?链表是不是可能存在环?数组的长度是不是零?输入的数据会不会完全不符合题意的要求?在示例中,边界条件就是当结点指针为空时,高度应该是0。当你察觉到边界条件存在时,就可以询问面试官处理方式,或者直接告诉面试官你认为什么样的处理方式是合理的。对边界条件的处理在开发软件时也异常重要。忽视了一个边界条件,就会对程序鲁棒性造成极大的影响,可能直接造成巨大经济损失甚至是人员伤亡。
在写代码的时候,尽量使用可读性较高的函数名和变量名。例如,要计算二叉树的深度,函数签名可以为int getHeightOfBinaryTree(TreeNode* root)
入参就叫root
(而非node
)。递归时,左子树的高度的变量名可以叫left_height
。诸如此类。这样操作的主要目的也是让面试官看到你良好的编码习惯。
实现算法的过程中,切忌闷头狂写而不与面试官交流。实际上,在写一些关键代码的时候,你完全可以告诉面试官你在实现什么功能。同样如前例计算二叉树深度,那你就可以告诉面试官,int left_height = getHeightOfBinaryTree(root->left)
是在计算左子树的高度(良好的函数名和变量名其实也让这行代码不言自明),而int root_height= max(left_height, right_height) + 1
则是根据左子树和右子树的高度计算当前根节点的高度。
当然了,在这个简单的示例中,交流或许显得不是那么重要,但是在一些复杂的问题中交流可能会非常重要。例如,示例的follow-up是请不用递归实现同样的功能,或者更进一步,请用常数空间实现同样的功能。在这样的问题中(代码可能长达数十行),交流就至关重要了。面试官需要和你交流来理解你的思路与状态,你同样需要交流来理清思路。这种写代码过程中的交流也是正式工作时非常重要的能力。
在你写完代码之后,不要急着告诉面试官你已经写完了。最好先手动跑一个/数个简单的样例。注意跑这个样例的过程要让面试官可以看见并轻易地理解,这常常是需要一些练习的。例如,我在Google Doc上跑样例的做法是,在屏幕上写出中间变量的当前取值,然后用鼠标光标告诉面试官现在程序跑到了哪一行代码,当前各个变量的取值是多少等等。主动测试的好处有很多。第一,这告诉面试官你很重视测试,而测试在实际生产中是非常非常重要的。第二,一个简单的样例常常可以找出不少类似于typo这样的小错误。第三,如果你的样例给得不错,那你甚至能够借助这个样例找到程序中的bug并纠正它,这总是要好过面试官发现并告诉你程序中存在着bug。主动测试时,你也可以确认你的程序可以很好地处理边界数据。
我自己在面一家外企的时候,主动测试的习惯就给我带了很大的回报。当时我写了一段不算复杂的程序(约20行左右),可是因为情绪紧张,程序中包含了一个相对隐蔽的bug。写完之后,我习惯性地跑了一个简单的样例,这花了我大约3分钟的时间,但却让我注意到了那个bug。我赶紧修复了这个bug。到了面试的提问环节,我问面试官本场面试中我表现最好的一点是什么。他告诉我:”是你通过一个样例发现了你的bug。实际上,在你写出了那段代码的时候我就注意到了这个bug,当时我在犹豫要不要提醒你。而你随即开始了测试并找到了这个bug。“这场面试的结果是,在面试结束半小时左右我就收到了通过面试的消息。
在写完代码之后,应当主动分析自己算法的时间与空间复杂度。一方面,这样可以展示自己扎实的算法基础。另一方面,这也可以告诉面试官自己有这方面的意识。当然了,如果复杂度分析的有误,那这个分析也可能会成为一个减分项。
有些时候,题目的解法可能存在一些trade-off。最常见的就是时间-空间的trade-off,当然有时也会有一些其他的trade-off。如果意识到了这道题目存在trade-off,那么可以主动地与面试官聊trade-off,让他/她知道你的思考过程与选择。
计算机基础部分的内容包括数据结构、操作系统、编程语言、计算机网络等等。这部分的准备很大程度上是需要一些扎实的基础的,再配合一些面试公司的面经。有些同学想仅仅靠看面经就应付过去,我可以说大多数情况下是不太可能的。有经验和水平的面试官可以轻易地通过几个follow-up问题来判断出来这名候选者是不是靠面经回答出来前面的问题的。当然了,面经对于这块内容仍然是非常有价值的,但阅读面经的时候要注意,并不能仅仅看一下某道题目的答案就够了,而是要看这个题目考察的是哪一块的知识,这一块知识自己有没有遗忘的、生疏的、不扎实的,如果有的话要去做相应的准备。「面经是告诉你这家公司面试的时候喜欢问哪些知识,而不是告诉你他们喜欢问哪些特定的问题」,虽然有的时候有些高频问题确实可能在你的面试中出现。
有些同学被问到一些自己会的基础知识的时候会特别激动,想抓住这个机会表现自己,就会事无巨细地回答一波。我个人认为,如果是基础知识的话,其实不用回答得特别详细,说出一些面试官想问的关键要点就可以了。有时候不一定能判断出来面试官想问的要点,这也不要紧,就说一些自己认为是关键的要点,然后等着面试管继续问follow-up就可以了。这里举一个简单的例子,如果面试官问进程与线程的区别,那么简单地说线程是调度的最小单位,同一个进程的线程共享地址空间,容易有线程安全问题;进程是多数资源分配的最小单位,所以进程的地址空间都是独立的,资源安全问题相对较少。回答到这个份上就差不多够了,然后等面试官继续问follow-up,而不需要去解释为什么线程会有安全问题等。之所以建议这么做,是因为对于有些公司,面试时间是有限制的(例如Google, hulu等),所以面试时间是很宝贵的,你应该用这珍贵的时间去展示自己的优势,而不是说一些绝大部分人都懂的trivial的知识。当然了,有经验/不nice的面试官可能会打断你,问他自己感兴趣想问的东西,但如果你运气不好恰好面试官没啥经验或者不喜欢打断人,那这样浪费宝贵的时间是很可惜的。
如果针对某个问题有自己一些独到的见解,或者是这个知识在很多教科书上可能看不到,很多同学也不一定知道,那么在回答问题的时候说出自己的这个insight,当然前提是自己的说法是有道理的。这里举一个简单的例子,比如一个面试问题是,可以用什么数据结构来实现队列。回答可以说是链表,接着可以补一句但是链表实现队列的性能不一定很好,因为链表节点的地址空间不是连续的,对cache不友好(小问题:那么如何改进这一点呢?)。这种知识其实是有一些经验的人或者基础扎实的人都知道的,不算是什么难点,但作为应届生,能直接说出这一点还是可能会让面试官觉得这个候选人基础不错。
如果在某些基础问题上自己有一些实际经验,那么可以结合自己的经验来回答,这样会让面试官觉得这个候选人不仅基础扎实、经验丰富,而且学以致用、分析问题的能力也挺强的。
这里举一个简单的例子,比如面试官问hash table处理冲突有哪些常用的方式,各有什么优缺点。那么可以回答常用的有线性探测和拉链法两种。如果自己有相应的经验,那么就可以结合经验谈谈优缺点,例如线性探测在实际使用的时候常常需要空间开得比较大,hash table的装载因子需要维持一个一直比较小的状态(比如25%-50%这样),否则的话性能就会很差,因为查询和插入都会频繁地进行长距离的线性探测。而拉链法对空间的利用效率就会比较高。在提供足够的空间的时候,按经验线性探测会比拉链法快很多,比如之前做了个项目,在满足空间条件的时候线性探测会快7倍左右(这是在结合经验谈),原因是线性探测比拉链法对cache更友好(这是基础知识)。
类似于这样的回答方式,可以让面试官留下一个很好的印象,认为这位候选人的整体素质也非常出色。
如果面试官是很熟悉这个领域、这类项目的人,那么你可以make some assumption,即不需要做多少背景介绍。否则的话,还是建议简单谈一下自己项目的背景是什么。这是因为在不同的背景下,同一种功能的实现常常会有不同的选择。这样的背景介绍能帮助面试官更好地理解这个项目,以及大概理解一些实现的选择。背景主要包括场景、问题定义、需求、自己负责的部分扮演的角色等等。
介绍完项目背景后,需要简单介绍一下自己这个项目的解决方案。解决方案主要是使用了什么技术、什么工具、怎么样的实现等等。需要注意的是,介绍解决方案的时候最好要结合场景一起说,否则会缺乏一些说服力。
这里仍然举个简单的例子。例如做深度学习的落地,深度学习框架选用的是腾讯的ncnn,那么最好说一下因为场景是嵌入式arm设备,且没有显卡,在这种场景下,ncnn做了很多指令级的优化,速度会更快。
针对项目中的困难点要特别认真地谈论一下,需要介绍为什么这个点是个困难点,解决方案大致是什么样的思路,为什么要这样去设计解决方案,最终达成了一个什么样的效果。如果一个候选人能展示出准确的痛点、瓶颈分析能力,并且能提出合理的解决方案的能力,那我相信面试官对他的评价会大大提升。
这里同样举一个简单的例子。例如做数据库实现,项目中有一个问题是数据库太大,不可能放到内存里,但如果都放硬盘的话又太慢,这是项目中的一个困难点。解决困难点的关键是同时利用内存的速度优势与硬盘的容量优势,设计一个存储分层模型。做实验观察到90%的针对数据库的查询仅集中在10%的数据上。那么解决方案可以是设计一个冷热分离的模型,仅仅在内存中存储一些热(即查询频繁)的数据,而将冷(即查询频率很低)的数据存在硬盘上,同时设定一定的策略定期做冷热数据替换。经过这样的设计之后,数据库的查询速度提升了30倍。
与项目不同,很多冷门的research的背景面试官往往是不了解的,所以常常需要做相对详细一些的背景介绍。
在面试之前,可以先自己精细地准备一下论文的介绍。假设这个面试官对这个领域不熟悉,如何才能让他在较短时间理解这个研究领域,大概明白领域的痛点,并理解你的论文的思路、解决方案与重要性呢?
强烈建议在面试之前找人模拟一下,并让对方给你一些反馈。这样能够大大降低紧张感,熟悉面试流程并提高面试表现。当然了,还有一个重要的方式就是多多投递,先拿一些自己不target的公司练练手,磨练自己的心态与面试技巧。
我也曾当过几次面试官,也参加过一些面试并了解过其他人的面试情况,这里简单说几条面试大忌,一定要避免犯的错误。
对自己不懂的东西(甚至是没有十成把握的东西),一定要诚实地说出来,千万不要不懂装懂。我把这一点放在最前面,是因为我作为面试官以及平时与人讨论技术的时候,就非常讨厌别人不懂装懂。面试官的水平往往比你高很多,一下子就能判断出来你是真懂还是装懂。所以,碰到自己不懂或者没把握的问题,我建议直接告诉面试官说这个问题我没把握,不是很懂。但如果你有一些思路的话,可以接着说“虽然我不太懂,但是可以试着说一下”,这就可以变成一个展示你解决问题分析问题能力的机会了。而如果你的分析思路很合理,得出的结论也大差不差,那甚至可以很大程度地提升面试官对你的评价。
面试的时候,人要有自信,但是态度一定要平和并且尊重面试官,切不可恃才傲物、狂傲不羁。有一些公司会非常看重这一点,如果你给面试官留下了不好沟通的印象,那往往是一票否决。但面试的时候,偶尔也会碰到面试官不是很懂犯错误的情况(比如国内的一些大厂),这个时候你最好是平和地去与面试官讨论,如果他坚持不肯认错,那你也不要去较真,否则的话可能你面试就挂了。有一种情况是可以去与面试官较真的,那就是你完全不在乎这家公司的offer,这时候你可以放开了较真哈哈哈。另一方面,当你面试一家公司或者一个组,碰到面试官不懂装懂又不肯认错的时候,你也得考虑一下这个组是不是值得你去。
面试总会有运气成分与偶然性,放平心态,不要因为害怕被拒就不敢投递,也不要因为患得患失而在面试的时候十分紧张。在面试中尽量让自己自然、轻松。当然,一些轻微地紧张有时是可以让自己发挥更好的,但是要适度,切不可紧张过头。面试中即使有些内容答得不好,也不要当场就心态崩盘,要沉着应付。当自己没有什么思路的时候也不要太慌,可以试着从基本的地方开始分析。例如做算法题,可以分析一些toy example,有时候能获得一些思路。回答CS基础题、system design等题目也可以从基础的地方开始分析,甚至是与面试官一起一步一步得出结果。我自己在参加一次面试的时候,一道算法题问清楚题目就花了10多分钟,然后10多分钟没有思路,同时面试官还在给我施加一定的压力。要知道面试总共就45分钟,这样的表现属于非常糟糕的了。所幸我当时稳住了心态,利用一个toy example得到了正确的思路,写出了bug-free的代码,最后还是让面试官相当满意。
需要说明的是,每个人都有自己的面试风格,很多面试官也会有自己的喜好,所以没有一套universal的面试方案。本文提到的一些技巧什么的,主要是我自己总结出来适用于自己的风格与方案,读者完全可以根据自己的实际情况与面试时候的感受来调整。举个例子,本文提到的编程部分心得,主要是针对Google这样的公司的算法题部分。我自己也有过一些面试经历,面试官非常不喜欢候选人在写代码的时候与他交流,甚至会在你写代码的时候自己去做别的事情 :( 这时候你最好就乖乖闭嘴,把代码写出来即可:) 因此,也希望各位因时制宜,因地制宜,结合实际情况来进行面试。最后祝大家都能有满意的offer~
(完)