博文

Google女性工程师揭密:如何准备Google软件工程师面试(2007-01-01 11:43:00)

摘要:(作者简介: 王忻,Google 工程师。北京出生,五岁时跟随父母移居美国。中学期间跳了三级,十五岁进入了加州理工大学,加入 Google 前曾在微软等公司工作。)

六月份的时候,我曾经在黑板报上介绍过“如何写一份好的工程师简历”, 今天想跟大家来谈谈如何准备软件工程师的面试?假设,现在您的杀手简历 (killer resume)已经吸引了某大公司的注意并约你面试。那么接下来该如何准备呢?

我 在 Google(以前是微软)工作期间面试了不下 300人,其中某些应聘者确实表现非凡,但有些却显得准备不足。当然许多面试准备不足的人最后依然获得了录用通知,因为他们本身确实才华出众。但如果应聘 者能提前准备妥当,那么面试过程将更为保险和轻松。以下所列出的就是我根据多年经验总结得出的建议:

1.使用相同的工具(如铅笔和纸张)和时间限制(例如半个小时)模拟面试训练

Google 和微软都会让应聘者在白板上手工解答编程问题,但通常大部分的应聘者都是习惯于在电脑上利用编程工具系统编写程序。因此面试的时候,某些应聘者离开了熟悉 的电脑光标,站在白板前感觉手足无措不知该如何起行。又或者他们不习惯在编程之时旁边有人观看,这会让他们感到紧张而无法正常思考。

在现实生活中,如果你想要横渡英吉利海峡,自然不能总是在室内游泳池练习。你必须投身于大海在波涛之中训练,在准备面试的时候也是如此。:)

在 面试开始之前你最好向招聘单位询问面试形式和面试问题。如果招聘单位让你在某个房间考试且仅提供没有汇编程序的编辑器,那么就应该在家中按照这种情景进行 练习。如果招聘公司单位让你在白板上回答问题并会安排考官在旁监督,那么你就要找一位软件工程师来扮演考官配合你练习。即使找来的考官经验不如你也没有关 系,他们依然能帮助你消除在他人面前出错所带来的紧张感,这样可以让你适应有人在旁边盯着看的面试氛围。

如果你恰巧认识我并希望由我来帮你联系,那我的条件就是必须请我吃饭:如果你已经工作了就吃日本寿司大餐;如果你还是学生,那么吃比萨饼也可以。:)

2.在面试过程中不要对细小错误耿耿于怀

我 曾不止一次的在面试过程中碰到这种情况:当应聘者知道编程问题后,他马上就想到了最佳的方案、确定了边界条件,然后......

阅读全文(1971) | 评论:0

我的世界观--阿尔伯特·爱因斯坦 (2006-07-28 11:18:00)

摘要:  我们这些总有一死的人的命运多么奇特!我们每个人在这个世界上都只作一个短暂的逗留;目的何在,却无从知道,尽管有时自以为对此若有所感。但是,不必深思,只要从日常生活就可以明白:人是为别人而生存的──首先是为那样一些人,我们的幸福全部依赖于他们的喜悦和健康;其次是为许多我们所不认识的人,他们的命运通过同情的纽带同我们密切结合在一起。我每天上百次的提醒自己:我的精神生活和物质生活都是以别人(包括生者和死者)的劳动为基础的,我必须尽力以同样的分量来报偿我所领受了的和至今还在领受着的东西。我强烈地向往着俭朴的生活。并且时常发觉自己占用了同胞的过多劳动而难以忍受。我认为阶级的区分是不合理的,它最后所凭借的是以暴力为根据。我也相信,简单淳朴的生活,无论在身体上还是在精神上,对每个人都是有益的。     我完全不相信人类会有那种在哲学意义上的自由。每一个人的行为不仅受着外界的强制,而且要适应内在的必然。叔本华说:“人虽然能够做他所想做的,但不能要他所想要的。”这句格言从我青年时代起就给了我真正的启示;在我自己和别人的生活面临困难的时候,它总是使我们得到安慰,并且是宽容的持续不断的源泉。这种体会可以宽大为怀地减轻那种容易使人气馁的责任感,也可以防止我们过于严肃地对待自己和别人;它导致一种特别给幽默以应有地位的人生观。     要追究一个人自己或一切生物生存的意义或目的,从客观的观点看来,我总觉得是愚蠢可笑的。可是每个人都有一些理想,这些理想决定着他的努力和判断的方向。就在这个意义上,我从来不把安逸和享乐看作生活目的本身──我把这种伦理基础叫做猪栏的理想。照亮我的道路,是善、美和真。要是没有志同道合者之间的亲切感情,要不是全神贯注于客观世界──那个在艺术和科学工作领域里永远达不到的对象,那么在我看来,生活就会是空虚的。我总觉得,人们所努力追求的庸俗目标──财产、虚荣、奢侈的生活──都是可鄙的。     我有强烈的社会正义感和社会责任感,但我又明显地缺乏与别人和社会直接接触的要求,这两者总是形成古怪的对照。我实在是一个“孤独的旅客”,我未曾全心全意地属于我的国家、我的家庭、我的朋友,甚至我最为接近的亲人;在所有这些关系面前,我总是感觉到一定距离而且需要保持孤独──而这种感受正与年俱增。人们会清楚地发觉,同别人的相互了解和协调一致是有限度的,但......

阅读全文(1830) | 评论:0

[转载]常用算法设计方法 - 动态规划法(2006-07-28 11:01:00)

摘要:  经常会遇到复杂问题不能简单地分解成几个子问题,而会分解出一系列的子问题。简单地采用把大问题分解成子问题,并综合子问题的解导出大问题的解的方法,问题求解耗时会按问题规模呈幂级数增加。 
  为了节约重复求相同子问题的时间,引入一个数组,不管它们是否对最终解有用,把所有子问题的解存于该数组中,这就是动态规划法所采用的基本方法。以下先用实例说明动态规划方法的使用。 
【问题】   求两字符序列的最长公共字符子序列 
问题描述:字符序列的子序列是指从给定字符序列中随意地(不一定连续)去掉若干个字符(可能一个也不去掉)后所形成的字符序列。令给定的字符序列X=“x0,x1,…,xm-1”,序列Y=“y0,y1,…,yk-1”是X的子序列,存在X的一个严格递增下标序列<i0,i1,…,ik-1>,使得对所有的j=0,1,…,k-1,有xij=yj。例如,X=“ABCBDAB”,Y=“BCDB”是X的一个子序列。 
给定两个序列A和B,称序列Z是A和B的公共子序列,是指Z同是A和B的子序列。问题要求已知两序列A和B的最长公共子序列。 
如采用列举A的所有子序列,并一一检查其是否又是B的子序列,并随时记录所发现的子序列,最终求出最长公共子序列。这种方法因耗时太多而不可取。 
考虑最长公共子序列问题如何分解成子问题,设A=“a0,a1,…,am-1”,B=“b0,b1,…,bm-1”,并Z=“z0,z1,…,zk-1”为它们的最长公共子序列。不难证明有以下性质: 
(1)   如果am-1=bn-1,则zk-1=am-1=bn-1,且“z0,z1,…,zk-2”是“a0,a1,…,am-2”和“b0,b1,…,bn-2”的一个最长公共子序列; 
(2)   如果am-1!=bn-1,则若zk-1!=am-1,蕴涵“z0,z1,…,zk-1”是“a0,a1,…,am-2”和“b0,b1,…,bn-1”的一个最长公共子序列; 
(3)   如果am-1!=bn-1,则若zk-......

阅读全文(2556) | 评论:0

[转载]常用算法设计方法 - 分治法(2006-07-28 11:00:00)

摘要:1、分治法的基本思想 
任何一个可以用计算机求解的问题所需的计算时间都与其规模N有关。问题的规模越小,越容易直接求解,解题所需的计算时间也越少。例如,对于n个元素的排序问题,当n=1时,不需任何计算;n=2时,只要作一次比较即可排好序;n=3时只要作3次比较即可,…。而当n较大时,问题就不那么容易处理了。要想直接解决一个规模较大的问题,有时是相当困难的。 
分治法的设计思想是,将一个难以直接解决的大问题,分割成一些规模较小的相同问题,以便各个击破,分而治之。 
如果原问题可分割成k个子问题(1<k≤n),且这些子问题都可解,并可利用这些子问题的解求出原问题的解,那么这种分治法就是可行的。由分治法产生的子问题往往是原问题的较小模式,这就为使用递归技术提供了方便。在这种情况下,反复应用分治手段,可以使子问题与原问题类型一致而其规模却不断缩小,最终使子问题缩小到很容易直接求出其解。这自然导致递归过程的产生。分治与递归像一对孪生兄弟,经常同时应用在算法设计之中,并由此产生许多高效算法。 
2、分治法的适用条件 
分治法所能解决的问题一般具有以下几个特征: 
(1)该问题的规模缩小到一定的程度就可以容易地解决; 
(2)该问题可以分解为若干个规模较小的相同问题,即该问题具有最优子结构性质; 
(3)利用该问题分解出的子问题的解可以合并为该问题的解; 
(4)该问题所分解出的各个子问题是相互独立的,即子问题之间不包含公共的子子问题。 
上述的第一条特征是绝大多数问题都可以满足的,因为问题的计算复杂性一般是随着问题规模的增加而增加;第二条特征是应用分治法的前提,它也是大多数问题可以满足的,此特征反映了递归思想的应用;第三条特征是关键,能否利用分治法完全取决于问题是否具有第三条特征,如果具备了第一条和第二条特征,而不具备第三条特征,则可以考虑贪心法或动态规划法。第四条特征涉及到分治法的效率,如果各子问题是不独立的,则分治法要做许多不必要的工作,重复地解公共的子问题,此时虽然可用分治法,但一般用动态规划法较好。 
3、分治法的基本步骤 
分治法在每一层递归上都有三个步骤: 

阅读全文(2065) | 评论:0

[转载]常用算法设计方法 - 贪婪法(2006-07-28 10:59:00)

摘要:  贪婪法是一种不追求最优解,只希望得到较为满意解的方法。贪婪法一般可以快速得到满意的解,因为它省去了为找最优解要穷尽所有可能而必须耗费的大量时间。贪婪法常以当前情况为基础作最优选择,而不考虑各种可能的整体情况,所以贪婪法不要回溯。 
  例如平时购物找钱时,为使找回的零钱的硬币数最少,不考虑找零钱的所有各种发表方案,而是从最大面值的币种开始,按递减的顺序考虑各币种,先尽量用大面值的币种,当不足大面值币种的金额时才去考虑下一种较小面值的币种。这就是在使用贪婪法。这种方法在这里总是最优,是因为银行对其发行的硬币种类和硬币面值的巧妙安排。如只有面值分别为1、5和11单位的硬币,而希望找回总额为15单位的硬币。按贪婪算法,应找1个11单位面值的硬币和4个1单位面值的硬币,共找回5个硬币。但最优的解应是3个5单位面值的硬币。 
【问题】   装箱问题 
问题描述:装箱问题可简述如下:设有编号为0、1、…、n-1的n种物品,体积分别为v0、v1、…、vn-1。将这n种物品装到容量都为V的若干箱子里。约定这n种物品的体积均不超过V,即对于0≤i<n,有0<vi≤V。不同的装箱方案所需要的箱子数目可能不同。装箱问题要求使装尽这n种物品的箱子数要少。 
  若考察将n种物品的集合分划成n个或小于n个物品的所有子集,最优解就可以找到。但所有可能划分的总数太大。对适当大的n,找出所有可能的划分要花费的时间是无法承受的。为此,对装箱问题采用非常简单的近似算法,即贪婪法。该算法依次将物品放到它第一个能放进去的箱子中,该算法虽不能保证找到最优解,但还是能找到非常好的解。不失一般性,设n件物品的体积是按从大到小排好序的,即有v0≥v1≥…≥vn-1。如不满足上述要求,只要先对这n件物品按它们的体积从大到小排序,然后按排序结果对物品重新编号即可。装箱算法简单描述如下: 
{   输入箱子的容积; 
  输入物品种数n; 
  按体积从大到小顺序,输入各物品的体积; 
  预置已用箱子链为空;&......

阅读全文(1719) | 评论:0

[转载]常用算法设计方法 - 回溯法(2006-07-28 10:59:00)

摘要:  回溯法也称为试探法,该方法首先暂时放弃关于问题规模大小的限制,并将问题的候选解按某种顺序逐一枚举和检验。当发现当前候选解不可能是解时,就选择下一个候选解;倘若当前候选解除了还不满足问题规模要求外,满足所有其他要求时,继续扩大当前候选解的规模,并继续试探。如果当前候选解满足包括问题规模在内的所有要求时,该候选解就是问题的一个解。在回溯法中,放弃当前候选解,寻找下一个候选解的过程称为回溯。扩大当前候选解的规模,以继续试探的过程称为向前试探。 
1、回溯法的一般描述 
可用回溯法求解的问题P,通常要能表达为:对于已知的由n元组(x1,x2,…,xn)组成的一个状态空间E={(x1,x2,…,xn)∣xi∈Si ,i=1,2,…,n},给定关于n元组中的一个分量的一个约束集D,要求E中满足D的全部约束条件的所有n元组。其中Si是分量xi的定义域,且 |Si| 有限,i=1,2,…,n。我们称E中满足D的全部约束条件的任一n元组为问题P的一个解。 
解问题P的最朴素的方法就是枚举法,即对E中的所有n元组逐一地检测其是否满足D的全部约束,若满足,则为问题P的一个解。但显然,其计算量是相当大的。 
我们发现,对于许多问题,所给定的约束集D具有完备性,即i元组(x1,x2,…,xi)满足D中仅涉及到x1,x2,…,xi的所有约束意味着j(j<i)元组(x1,x2,…,xj)一定也满足D中仅涉及到x1,x2,…,xj的所有约束,i=1,2,…,n。换句话说,只要存在0≤j≤n-1,使得(x1,x2,…,xj)违反D中仅涉及到x1,x2,…,xj的约束之一,则以(x1,x2,…,xj)为前缀的任何n元组(x1,x2,…,xj,xj+1,…,xn)一定也违反D中仅涉及到x1,x2,…,xi的一个约束,n≥i>j。因此,对于约束集D具有完备性的问题P,一旦检测断定某个j元组(x1,x2,…,xj)违反D中仅涉及x1,x2,…,xj的一个约束,就可以肯定,以(x1,x2,…,xj)为前缀的任何n元组(x1,x2,…,xj,xj+1,…,xn)都不会是问题P的解,因而就不必去搜索它们、检测它们。回溯法正是针对这类问题,利用这类问题的上述性质而提出来的比枚举法效率更高......

阅读全文(2144) | 评论:0

[转载]常用算法设计方法 - 递归(2006-07-28 10:58:00)

摘要:  递归是设计和描述算法的一种有力的工具,由于它在复杂算法的描述中被经常采用,为此在进一步介绍其他算法设计方法之前先讨论它。 
  能采用递归描述的算法通常有这样的特征:为求解规模为N的问题,设法将它分解成规模较小的问题,然后从这些小问题的解方便地构造出大问题的解,并且这些规模较小的问题也能采用同样的分解和综合方法,分解成规模更小的问题,并从这些更小问题的解构造出规模较大问题的解。特别地,当规模N=1时,能直接得解。 
【问题】   编写计算斐波那契(Fibonacci)数列的第n项函数fib(n)。 
  斐波那契数列为:0、1、1、2、3、……,即: 
    fib(0)=0; 
    fib(1)=1; 
    fib(n)=fib(n-1)+fib(n-2)     (当n>1时)。 
写成递归函数有: 
int fib(int n) 
{   if (n==0)     return 0; 
  if (n==1)     return 1; 
  if (n>1)     return fib(n-1)+fib(n-2); 

  递归算法的执行过程分递推和回归两个阶段。在递推阶段,把较复杂的问题(规模为n)的求解推到比原问题简单一些的问题(规模小于n)的求解。例如上例中,求解fib(n),把它推到求解fib(n-1)和fib(n-2)。也就是说,为计算fib......

阅读全文(1907) | 评论:0

[转载]常用算法设计方法 - 递推法(2006-07-28 10:57:00)

摘要:  递推法是利用问题本身所具有的一种递推关系求问题解的一种方法。设要求问题规模为N的解,当N=1时,解或为已知,或能非常方便地得到解。能采用递推法构造算法的问题有重要的递推性质,即当得到问题规模为i-1的解后,由问题的递推性质,能从已求得的规模为1,2,…,i-1的一系列解,构造出问题规模为I的解。这样,程序可从i=0或i=1出发,重复地,由已知至i-1规模的解,通过递推,获得规模为i的解,直至得到规模为N的解。 
【问题】   阶乘计算 
问题描述:编写程序,对给定的n(n≦100),计算并输出k的阶乘k!(k=1,2,…,n)的全部有效数字。 
由于要求的整数可能大大超出一般整数的位数,程序用一维数组存储长整数,存储长整数数组的每个元素只存储长整数的一位数字。如有m位成整数N用数组a[ ]存储: 
  N=a[m]×10m-1+a[m-1]×10m-2+ … +a[2]×101+a[1]×100 
并用a[0]存储长整数N的位数m,即a[0]=m。按上述约定,数组的每个元素存储k的阶乘k!的一位数字,并从低位到高位依次存于数组的第二个元素、第三个元素……。例如,5!=120,在数组中的存储形式为: 
3   0   2   1   …… 
首元素3表示长整数是一个3位数,接着是低位到高位依次是0、2、1,表示成整数120。 
  计算阶乘k!可采用对已求得的阶乘(k-1)!连续累加k-1次后求得。例如,已知4!=24,计算5!,可对原来的24累加4次24后得到120。细节见以下程序。 
# include <stdio.h> 
# include <malloc.h> 
# define MAXN   1000 
void&n......

阅读全文(1334) | 评论:0

[转载]常用算法设计方法 - 穷举搜索法(2006-07-28 10:56:00)

摘要:  穷举搜索法是对可能是解的众多候选解按某种顺序进行逐一枚举和检验,并从众找出那些符合要求的候选解作为问题的解。 
【问题】   将A、B、C、D、E、F这六个变量排成如图所示的三角形,这六个变量分别取[1,6]上的整数,且均不相同。求使三角形三条边上的变量之和相等的全部解。如图就是一个解。 
程序引入变量a、b、c、d、e、f,并让它们分别顺序取1至6的证书,在它们互不相同的条件下,测试由它们排成的如图所示的三角形三条边上的变量之和是否相等,如相等即为一种满足要求的排列,把它们输出。当这些变量取尽所有的组合后,程序就可得到全部可能的解。细节见下面的程序。 
【程序1】 
# include <stdio.h> 
void main() 
{   int a,b,c,d,e,f; 
  for (a=1;a<=6;a++)   
    for (b=1;b<=6;b++)     { 
      if (b==a)     continue; 
      for (c=1;c<=6;c++)     { 
        if (c==a)||(c==b)   continue; 
        for (d=1;d<=6;d+......

阅读全文(1940) | 评论:0

[转载]常用算法设计方法 - 迭代法(2006-07-28 10:52:00)

摘要:  迭代法是用于求方程或方程组近似根的一种常用的算法设计方法。设方程为f(x)=0,用某种数学方法导出等价的形式x=g(x),然后按以下步骤执行: 
(1)   选一个方程的近似根,赋给变量x0; 
(2)   将x0的值保存于变量x1,然后计算g(x1),并将结果存于变量x0; 
(3)   当x0与x1的差的绝对值还小于指定的精度要求时,重复步骤(2)的计算。 
若方程有根,并且用上述方法计算出来的近似根序列收敛,则按上述方法求得的x0就认为是方程的根。上述算法用C程序的形式表示为: 
【算法】迭代法求方程的根 
{   x0=初始近似根; 
  do { 
    x1=x0; 
    x0=g(x1);   /*按特定的方程计算新的近似根*/ 
    } while ( fabs(x0-x1)>Epsilon); 
  printf(“方程的近似根是%f\n”,x0); 

迭代算法也常用于求方程组的根,令 
    X=(x0,x1,…,xn-1) 
设方程组为: 
    xi=gi(X)     (I=0,1,…,n-1) 
则求方程组根的迭代算法可描述如下: 
【算法】迭代法求方程组的根 
  {   for (i=0;i<n;i++) 
  &n......

阅读全文(2070) | 评论:0