(转载自 http://zh.lucida.me/blog/whiteboard-coding-demystified/)程序员
面试很困难,技术面试更加困难——只用 45 ~ 60 分钟是很难考察出面试者的水平的。因此 刘未鹏 在他的 怎样花两年时间去面试一我的 一文中鼓励面试者建立 GitHub 帐号,阅读技术书籍,创建技术影响力,从而提供给面试官真实,明确,可度量的经历。面试
这种方法对面试者效果很好,但对面试官效果就很通常——面试官要面对大量的面试者,这些面试者之中可能只有不多人拥有技术博客,但这并不表明他们的技术能力不够强(也许他们对写做不感兴趣);另外一方面,一些人拥有技术博客,但这也不能说明他们的水平就必定会很牛(也许他们在嘴遁呢)。算法
总之,技术博客和 GitHub 帐号是加分项,但技术面试仍然必不可少。因此,问题又回来了,如何进行高效的技术面试?或者说,如何在 45 ~ 60 分钟内尽量准确的考察出面试者的技术水平?编程
回答这个问题以前,让咱们先看下技术面试中的常见问题都有什么:小程序
技术面试中的常见问题
技术面试中的问题大体能够分为 5 类:数组
编码:考察面试者的编码能力,通常要求面试者在 20 ~ 30 分钟以内编写一段需求明确的小程序(例:编写一个函数划分一个整形数组,把负数放在左边,零放在中间,正数放在右边);
设计:考察面试者的设计/表达能力,通常要求面试者在 30 分钟左右内给出一个系统的大体设计(例:设计一个相似微博的系统)
项目:考察面试者的设计/表达能力以及其简历的真实度(例:描述你作过的 xxx 系统中的难点,以及你是如何克服这些难点)
脑筋急转弯:考察面试者的『反应/智力』(例:若是你变成蚂蚁大小而后被扔进一个搅拌机里,你将如何脱身?)
查漏:考察面试者对某种技术的熟练度(例:Java 的基本类型有几种?)
这 5 类问题中,脑筋急转弯在外企中早已绝迹(由于它没法断定面试者的真实能力),查漏类问题由于实际价值不大(毕竟咱们能够用 Google)在外企中出现率也愈来愈低,剩下的 3 类问题里,项目类和设计类问题要求面试官拥有同类项目经验,只有编码类问题不须要任何前提,因此,几乎全部的技术面试中都包含编码类问题。网络
然而,最令面试者头痛的也是这些编码类问题——由于几乎全部的当面(On-site)技术面试均要求面试者在白板上写出代码,而不是在面试者熟悉的 IDE 或是编辑器中写出。在个人面试经历里,不止一个被面试者向我抱怨:『若是能在计算机上编程,我早就把它搞定了!』就连我本身在面试初期也曾怀疑白板代码的有效性:『为何不让面试者在计算机上写代码呢?』数据结构
然而在经历了若干轮被面试与面试以后,我惊奇的发现白板编程居然是一种至关有效的技术考察方式。这也是我写这篇文章的缘由——我但愿经过这篇文章来阐述为何要进行白板编程(WHY),什么是合适的白板编程题目(WHAT),以及如何进行白板编程(HOW),从而既帮助面试者更好的准备面试,也帮助面试官更好的进行面试。数据结构和算法
为何要进行白板编程
不少面试者但愿可以在 IDE 中(而不是白板上)编写代码,由于:编辑器
主流 IDE 均带有智能提示,从而大大提高了编码速度
IDE 能够保证程序可以编译经过
能够经过 IDE 运行/调试代码,找到程序的 Bug
我认可第 1 点,白板编程要比 IDE 编程慢不少,但这并不能作为否定白板编程的理由——由于白板编程每每是 API 无关(所以并不须要你去背诵 API)的一小段(通常不超过 30 行)代码,并且面试官也会容许面试者进行适当的缩写(好比把Iterable类型缩写为Iter),所以它并不能成为否定白板编程的理由。
至于第 2 点和第 3 点,它们更不能成为否定白板编程的借口——若是你使用 IDE 只是为了在其帮助下写出能过编译的代码,或是为了调试改 Bug,那么我不认为你是一名合格的程序员——我认为程序员能够被分为两种:
先确认前条件/不变式/终止条件/边界条件,而后写出正确的代码
先编写代码,而后经过各类用例/测试/调试对程序进行调整,最后获得彷佛正确的代码
我我的保守估计前者开发效率至少是后者的 10 倍,由于前者不须要浪费大量时间在 编码-调试-编码 这个极其耗时的循环上。经过白板编程,面试官能够有效的断定出面试者属于前者仍是后者,从而招进合适的人才,并把老油条或是嘴遁者排除在外。
除了断定面试者的开发效率,白板编程还有助于展现面试者的编程思路,并便于面试者和面试官进行交流:
白板编程的目标并非要求面试者一会儿写出天衣无缝的代码,而是:
让面试者在解题的过程当中将他/他的思惟过程和编码习惯展示在面试官面前,以便面试官断定面试者是否具有清晰的逻辑思惟和良好的编程素养
若是面试者陷入困境或是陷阱,面试官也能够为其提供适当的辅助,以避免面试陷入无人发言的尴尬境地
正如前文所述,白板编程是一种颇有效的技术面试方式,但这是创建在有效的编程题目的基础之上:若是编程题目过难,那么面试极可能会陷入『大眼瞪小眼』的境地;若是编程题目过于简单(或者面试者背过题目),那么面试者无需思考就能够给出正确答案。这两种状况都没法达到考察面试者思惟过程的目的,从而使得面试官没法正确评估面试者的能力。
既然编程题目很重要,那么问题来了,什么才是合适(合理)的编程题目呢?
在回答这个问题以前,让咱们先看看什么编程题目不合适:
我在求职时发现,技术面试的编程题目每每千篇一概——拿我本身来讲,反转单链表被问了 5 次,数字转字符串被问了 4 次,随机化数组被问了 3 次,最好笑的是在面试某外企时三个面试官都问我如何反转单链表,以致于我得主动要求更换题目以避免误会。
无独有偶,我在求职时同时发现不少面试者都随身带一个本子或是打印好的材料,上面写满了常见的面试题目,一些面试者甚至会祈祷可以被问到上面的题目。
就这个问题,我和个人同窗以及后来的同事讨论过,答案是不少面试官在面试前并不会提早准备面试题,而是从网络上(例如 July 的算法博客)或 编程之美 之类的面试题集上随机挑一道题目询问。若是面试者作出来(或背出来)题目那么经过,若是面试者作不出来就挂掉。
这种面试方式的问题很是明显:若是面试者准备充分,那么这些题目根本没有区分度——面试者极可能会把答案直接背下来;若是面试者未作准备,他/她极可能被一些须要 aha! moment 的题目困住。总之,若是面试题不能评估面试者水平,那么问它还有什么意义呢?
下面是一些问滥的编程问题:
白板编程的目标在于考察面试者的编程基本功,而不是考察面试者使用某种语言/类库的熟练度。因此白板编程题目应尽量库函数无关——例如:编写一个 XML 读取程序就是不合格的题目,由于面试者没有必要把 XML 库中的函数名背下来(否则要 Intellisense 干甚);而原地消除字符串的重复空白(例:"ab c d e"
=> "ab c d e"
)则是一道合格的题目,由于即使不使用库函数,合格的面试者也可以在 20 分钟内完成这道题目。
这类问题相似 被问滥的编程问题,它们的特色在于过于直接,以致于面试者不须要思考就能够给出答案,从而使得面试官没法考察面试者的思惟过程。快速排序,深度优先搜索,以及二分搜索都属于这类题目。
须要注意的是,尽管过于直接的算法题目不适合面试,可是咱们能够将其进行一点改动,从而使其变成合理的题目,例如稳定划分和二分搜索计数(给出有序数组中某个元素出现的次数)就不错,尽管它们实际是快速排序和二分搜索的变种。
同 过于直接的算法问题< 相反,过于复杂的题目 属于另外一个极端:这些题目每每要求面试者拥有极强的算法背景,尽管算法问题是否过于复杂因人而异(在一些 ACM 编程竞赛选手的眼里可能就没有复杂的题目 -_-),但我我的认为若是一道题知足了下面任何一点,那么它就太复杂,不适合面试(不过若是面试者是 ACM 编程竞赛选手,那么能够无视此规则):
什么是脑筋急转弯?
在一些书(例如 谁是谷歌想要的人才?:破解世界最顶尖公司的面试密码)和电影的渲染下,Google 和微软这些外企的面试被搞的无比神秘,以致于不少人觉得外企真的会问诸如『井盖为何是圆的』或是『货车能装多少高尔夫球』这样的奇诡问题。而实际上,这些题目因为没法考察面试者的技术能力而早已在外企中绝迹。反却是一些国内公司开始使用脑筋急转弯 做为面试题目 -_-#
因此,技术面试题目不该该太难,也不该太简单,不能是脑筋急转弯,也不能直接来自网络。
前三点并不难知足:咱们能够去 算法导论,编程珠玑,以及 计算机程序设计艺术 这些经典算法书籍中的课后题/练习题挑选合适的题目,也能够本身创造题目。然而,因为 careercup 这类网站的存在,没有什么题目能够作到绝对原创——毕竟没有人能阻止面试者把题目发到网上,因此任何编程题目都逃脱不了被公开的命运。
不过,尽管面试者会把编程题目发到网上,甚至会有一些『好心人』给出答案,但这并不表明面试官不能继续使用这道题:由于尽管题目被公开,但题目的考察点和延伸问题依然只有面试官才知道。这有点像 公钥加密,公钥(面试题)是公开的,但私钥(解法,考察点,以及延伸问题)只有面试官才知道。这样即使面试者知道面试题,也不会妨碍面试官考察面试者的技术能力。
接下来,让咱们看看什么问题适合白板编程。
良好的编程问题都会有不止一种解法。这样面试者能够在短期内给出一个不那么聪明但可实现的『粗糙』算法,而后经过思考(或面试官提示)逐步获得更加优化的解法,面试官能够经过这个过程观察到面试者的思惟方式,从而对面试者进行更客观的评估。
以 数组最大子序列和 为例,它有一个很显然的 O(n^3) 解法,将 O(n^3) 解法稍加改动能够获得 O(n^2) 解法,利用分治思想,能够获得 O(n*logn) 解法,除此以外它还有一个 o(n) 解法。(编程珠玑 和 数据结构与算法分析 C语言描述 对这道题均有很是精彩的描述,有兴趣的朋友能够自行阅读)
良好的编程问题应拥有大量考察点,面试官应对这些考察点烂熟于心,从而给出更加客观量化的面试结果。这里能够参考我以前在 从武侠小说到程序员面试 提到的 to_upper
。
良好的编程问题应拥有延伸问题。延伸问题既能够应对面试者背题的状况,也能够渐进的(Incremental)考察面试者的编程能力,同时还保证了面试的延续性(Continuity)。
以 遍历二叉树 为例:面试官能够从非递归中序遍历二叉树开始提问,面试者有可能会很快的写(或是背)出一个使用栈的解法。这时面试官能够经过延伸问题来判别面试者是否在背题:使用常量空间中序遍历 带有父节点指针的二叉树,或是找到二叉搜索树中第 n 小的元素。下面是中序遍历二叉树的一些延伸问题:
1
2
3
4
5
6
7
8
9
10
11
|
|--中序遍历二叉树
|
|--非递归中序遍历二叉树
|
|--常量空间,非递归遍历带父节点的二叉树
| |
| |--在带父节点的二叉搜索树寻找第 N 小的元素
| |
| |--能否进一步优化时间复杂度?
|
|--常量空间,非递归遍历不带父节点的二叉树
|
上面的问题不但能够被正向使用(逐步增强难度),也能够被逆向使用(逐步下降难度):一样从非递归中序二叉树遍历开始提问,若是面试者没法完成这个问题,那么面试官能够下降难度,要求面试者编写一个递归版本的中序遍历二叉树。
面试以前,面试官应至少获得如下信息:
接下来,面试官应根据面试者的简历/职位确认对面试者的指望值,而后准备好编程题目(而不是面试时即兴选择题目)。面试官应至少准备 4 道题目(2 道简单题,2 道难题),以应对各类状况。
面试时,面试官应清楚的陈述题目,并经过若干组用例数据确认面试者真正的理解题目(以避免面试者花很长时间去作不相关的题目,我在以前的面试就办过这种挫事 -_-#)
在面试者解题时,面试官应全程保持安静(或倾听的状态),若是面试者犯下特别严重的错误或是陷入苦思冥想,面试官应给出适当的提示,以帮助面试者走出困境完成题目,若是面试者仍是不能完成题目,那么面试官应换一道略简单的题目,要知道面试的目的是发现面试者的长处,而非为难面试者。(一些国内企业彷佛正好相反)
面试以后,面试官应拍照(或誊写)面试者写下的代码,而后把提问的问题发给 HR 和接下来的面试者(以确保问题不会重复)。接下来,面试官应根据面试者的代码以及其面试表现,尽快写出面试反馈(Interview Feedback)发给 HR,以便接下来的招聘流程。
面试以前,面试者应至少作过如下准备:
肯定需求
面试者在白板编程时最重要的任务是理解题目,确认需求——肯定输入/输出,肯定数据范围,肯定时间/空间要求,肯定其它限制。以最多见的排序为例:
有时面试官不会把题目说的特别清楚,这时就须要面试者本身去确认这些需求,不要认为这是在浪费时间,不一样的需求会致使大相径庭的解法,此外确认需求会留给面试官良好的印象。
白板编程
理解题目确认需求以后,面试者就能够开始在白板上编写代码,下面是一些我本身的白板编程经验:
白板编程无法复制粘贴,因此后期调整代码结构很是困难。所以咱们最好在开头写出程序的大体结构,从而保证以后不会有大改;
咱们能够经过注释的形式给出代码的前条件/不变式/后条件,以划分为例:
1
2
3
4
5
6
7
8
9
|
int* partition(int *begin, int *end, int pivot) {
int *par = begin;
for ( ; begin < end; begin++) {
if (*begin < pivot) {
swap(begin, par++)
}
}
return par;
}
|
就不如
1
2
3
4
5
6
7
8
9
10
11
12
|
int* partition(int *begin, int *end, int pivot) {
// [begin, end) should be a valid range
int *par = begin;
// Invariant: All [0, par) < pivot && All [par, begin) >= pivot
for ( ; begin < end; begin++) {
if (*begin < pivot) {
swap(begin, par++)
}
}
// Now All [0, par) < pivot && All [par, end) >= pivot
return par;
}
|
尽管不变式足以验证程序的正确性,但适当的使用实例数据会大大加强代码的可信性,以上面的划分程序为例:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
|
Given range [2, 3, 4, 5, 1] and pivot 3
[ 2, 3, 4, 5, 1 ]
^ ^
p,b e
[ 2, 3, 4, 5, 1 ]
^ ^
p,b e
[ 2, 3, 4, 5, 1 ]
^ ^ ^
p b e
[ 2, 3, 4, 5, 1 ]
^ ^ ^
p b e
[ 2, 1, 4, 5, 3 ]
^ ^ ^
p b e
[ 2, 1, 4, 5, 3 ]
^ ^
p b,e
Now we have all [0, p) < 3 and all [p, e) >= 3
|
白板编程并不须要面试者在白板上写出可以一次经过编译的代码。为了节省时间,面试者能够在和面试官沟通的基础上使用缩写。例如使用 Iter
替代 Iterable
,使用 BQ
替代 BlockingQueue
。(此法尤为适合于 Java -_-#)
出于紧张或疏忽,通常面试者在白板编程时会犯下各类小错误,例如忘了某个判断条件或是漏了某条语句,空余的行宽能够帮助面试者快速修改代码,使得白板上的代码不至于一团糟。
这就延伸出了另外一个问题,若是使用大行宽,那么白板写不下怎么办?一些面试者聪明的解决了这个问题:他们在面试时会自带一根细笔迹的水笔,专门用于白板编程。
不会作怎么办
相信大多数面试者都碰到过面试题不会作的状况,这里说说我本身的对策:
我的不建议面试者在面试以后把题目发到网上,不少公司在面试前都会和面试者打招呼,有的会签定 NDA(Non Disclosure Agreement)条款以确保面试者不会泄露面试题目。尽管他们不多真的去查,但若是被查到那绝对是得不偿失。
我本身在面试以后会把面试中的编程题目动手写一遍(除非题目过于简单不值得),这样既可以验证本身写的代码,也能够保证本身不会在同一个地方摔倒两次。
书籍