作者 | Cooper Song
责编 | 伍杏玲
出品 | 程序人生(ID:coder_life)
笔者是一名双非院校计算机专业的大三学生,近期在找大厂的暑期实习,看到腾讯有在招聘2021届毕业的暑期实习生,就找了一位前辈内推。腾讯的效率还是很高的,当天晚上内推后,第二天中午就收到了电话,要求尽快面试,我便约了一个方便的时间。
我对腾讯的面试风格早有耳闻,那就是他们对于实习生和应届生开发经验相关的要求不多,更看重的是候选人的潜力(我理解的就是智商,可能稍有偏颇),现在看来也确实是这样。腾讯的暑期实习面试一般来说一共有三轮,两轮技术面试和一轮HR面试。腾讯的技术栈偏向于C/C++,因此遇到一个C/C++面试官的几率比较大。
在我准备完成最后一场技术面试拿Offer的时候,我竟因为一道看似很简单的题目被刷掉了。很多人都在说拿不拿Offer取决于候选人的算法好不好,我也是一直坚信如此的,然而今天,我却尝到了刷题的弊端。
笔者在大二期间参加过ACM、天梯赛等算法竞赛,都是铜奖;PAT甲级满分水平;LeetCode周赛有过AK的战绩。虽然不是什么大佬,但在一二线互联网公司程序员中也能算是中等水平了。然而,却败给了一道简单的代码题。虽然我心里也很清楚,菜才是原罪,可我想把自己的面试经历分享给大家,避免大家踩坑。本文将分享两轮面试经验并通过这些分析需要了解的基础知识、代码题和运气在大厂技术面试中所扮演的角色。
一轮技术面
一面持续了一个小时十五分钟左右,整个流程大概是先问基础后做题,考察的非常全面。
基础主要是四大块内容,一是编程语言,二是操作系统,三是计算机网络,四是数据结构与算法以及逻辑题。最重要的是最后一个部分,就是让很多程序员头疼的数据结构与算法。
编程语言部分问了一些C/C++相关的基础特性,甚至都没有问智能指针相关的内容。主要有如下几个问题:
C语言在两个不同的文件中定义的变量如何相互引用,我答的可以通过include头文件;
C语言中如下的字符指针声明语句哪些是指针不能被重新赋值,哪些是指针指向的值不能被重新赋值,char const * ptr、const char * ptr和char * const ptr,其实只有char * const ptr才是指针不能被重新赋值,其他的都是指针指向的值不能改变,这个很好记,const直接跟着ptr肯定ptr是不能被重新赋值的嘛;
C++的static关键字有什么作用;
什么是虚函数,虚函数有什么作用,虚函数机制是如何实现的,哪些函数可以声明成虚函数(面试官给了几个选项,分别是构造函数、析构函数、内联函数、友元函数、静态函数),要想回答上这个问题首先要熟悉构造函数、析构函数、内联函数、友元函数、静态函数的性质和作用;
操作系统部分问了如下几个问题:
什么是进程,这是基础概念,比较好答;
什么是线程,这也是基础概念,也比较好答;
进程与线程的区别和联系;
是否了解死锁,我先举了一个死锁的例子,然后回答了产生死锁的4个原因以及针对这4个原因采取的预防死锁的方法,其实还有一个避免死锁的银行家算法;
计算机网络部分主要围绕TCP/IP协议栈网络层以上内容问了如下几个问题:
TCP三次握手的过程;
TCP为什么要三次握手,一次握手不可以吗,两次握手不可以吗;
http协议相关;
从浏览器的网址栏输入一个域名需要经历的过程,http的底层是TCP,而TCP又需要IP,所以肯定要先答域名转IP的DNS协议,再三次握手,然后才是http请求相关的内容;
数据结构与算法以及逻辑题部分给我出了四道题目。
第一题考察面向对象思想以及抽象三维建模,让候选人用代码描述候选人所在的房间。于是我开始了我的描述,把房间抽象成一个类(对象),房间里有的桌子、椅子、杯子就成为了房间类的对象成员,每个成员都有自己所处的三维空间坐标x、y、z以及长length宽width高height,因此桌子、椅子、杯子也要写成不同的类。然而我在写代码的时候忘记总结它们的共性了,于是得到了面试官的提醒,我匆忙改正错误,说既然都有x、y、z坐标和长宽高,那就写一个共同的父类,面试官表示满意。
第二题是一道简单到不能再简单的数据结构题,输入一个二叉树得到二叉树的镜像,所谓镜像就是右边的节点换到左边,左边的节点换到右边,从根节点开始递归,先交换根节点的左右节点,再对根节点交换后的左孩子执行同样的操作,再对根节点交换后的右孩子执行同样的操作,就得到了二叉树的镜像。
第三题也是一道很简单的数据结构体,反转链表。算法竞赛思维此时开始作乱了。算法竞赛唯快不破,这里的快有两方面,一方面是通过控制算法的时间复杂度使执行时间不会超时,另一方面是在有限的时间内以最快的速度写出能够通过所有测试样例的代码。这两者之间需要权衡。比如有一道题目我确定了要使用归并排序,时间复杂度确定为O(nlogn),我自己写一个归并排序的时间复杂度是O(nlogn),我使用库函数sort进行排序的时间复杂度也是O(nlogn),为了最快时间写出代码,我会坚定地选择后者。我本着这一原则的想法就是遍历链表将元素都存放到vector里,然后直接reverse这个vector,最后遍历vector得到反转后的链表。面试官看到我这么玩赶紧制止了我,说不能使用STL。我心想好吧,反正自己写出来对于我来说也不是什么难事,于是写了一个pre指针一次遍历将这道题目解了出来。这个小插曲对于本场面试来说倒是无伤大雅,可也足够引起我的重视。
第四题是一个逻辑题。在一天夜里4个人需要过桥,要上桥必须要开着手电筒,否则就会发生危险,可是他们只有一把手电筒,并且只能亮17分钟,这4个人的过桥时间分别是1分钟、2分钟、5分钟和10分钟,请问如何在17分钟内让这四人平安过桥。我给大家提个醒,逻辑题不是脑筋急转弯!逻辑题不是脑筋急转弯!逻辑题不是脑筋急转弯!重要的话说三遍。面试官既然问如何在17分钟过桥,就说明一定有规则之内的解决方案。
这道题目类似于一个贪心算法,我起初尝试让1分钟那位老哥来回带着手电筒送人,在演绎了一会之后发现行不通。此时我主动向面试官自言自语,1分钟的老哥带着10分钟的老哥一起过桥对于1分钟的老哥来说不是很浪费嘛,面试官提示我我的想法是正确的。于是我化身柯南,说到了揭晓真相的时候了,1分钟的老哥与2分钟的老哥一起过桥,花费2分钟;然后1分钟的老哥回来送手电筒,花费1分钟;1分钟的老哥把手电筒给5分钟的老哥和10分钟的老哥,让他俩一块过桥,花费10分钟;那一头2分钟的老哥还在呢,2分钟的老哥是所有过桥者中速度最快的,于是他再回来送手电筒,花费2分钟;最后2分钟的老哥带着1分钟的老哥一起过桥,花费2分钟。总共花费2+1+10+2+2=17分钟。贪心的方法其实就是,回来送手电筒的一定是已经过桥的老哥中速度最快的,一起过桥的一定是速度最相近的且一起过桥的速度最快的。
二轮技术面
二面面试官在家给我面试的,看来是受疫情影响还没去公司。二面面试官一上来就出了一道代码题,正是这道题让我与腾讯无缘了。题目是这样描述的:
请补全这个函数string getInfo(int errCode),输入错误码返回错误信息,比如:
错误代码1,用户名错误;
错误代码2,密码错误;
错误代码10-100,数据库错误;
错误代码200-1000,文件找不到错误。
要求这个函数要使修改错误码和错误信息时尽可能少修改代码(代码可维护)。
我在想,这么简单直接if else就可以了嘛。
string getInfo(int errCode)
{
if(errCode==1)
{
return "密码用户名错误";
}
else if(errCode==2)
{
return "密码错误";
}
else if(errCode>=10&&errCode<=100)
{
return "数据库错误";
}
else if(errCode>=200&&errCode<=1000)
{
return "文件找不到错误";
}
}
我写着写着面试官开始阻止我,说让我看题目要求(代码可维护)。我再一次写偏了方向......代码画风变成了如下这样:
string getInfo(int errCode)
{
string ret0="密码用户名错误";
string ret1="密码错误";
string ret2="数据库错误";
string ret3="文件找不到错误";
if(errCode==1)
{
return ret0;
}
else if(errCode==2)
{
return ret1;
}
else if(errCode>=10&&errCode<=100)
{
return ret2;
}
else if(errCode>=200&&errCode<=1000)
{
return ret3;
}
}
于是面试官彻底对我失去兴趣,并说这样每次修改错误码及其对应的错误信息还是要把代码再修改一遍,然后直接告诉了我答案。他说可以把错误码和错误信息封装成结构体,所有错误码和错误信息构成结构体数组,每次都遍历一遍这个数组,找到符合的错误信息返回。
我开始反驳面试官,说我那样的写法时间复杂度是O(1),而变成你那样的写法时间复杂度就成了O(n),我以运行时间效率为标准写的代码,然后面试官反复强调代码可维护,我也只好作罢。此后面试官便对我失去了兴趣,于是问我还有什么可以聊聊的吗?我说TCP/IP协议栈,面试官说不行;我说https,面试官还说不行。他一直说他不想听背的内容,这些基础知识都是可以背过的。最后随便聊了聊项目就草草结束了。这里还要提一点的是,面试时间侧面上反应了面试通过的概率,因为面试官也是有工作要完成的,不可能花费时间去面试一个没有用的人。如果不到半小时就结束了,那么你大概率是挂了。
后来我仔细思考,面试官说的没有错!错的是我参加算法竞赛的思维,参加算法竞赛和做实际项目是有所不同的,可是为什么写成结构体数组就变得可维护了呢?请看下面的代码:
struct errInfo
{
int st;
int ed;
string info;
};
string getInfo(vector &arr,int errCode)
{
int len=arr.size();
for(int i=0;i=arr[i].st&&errCode<=arr[i].ed)
{
return arr[i].info;
}
}
return "错误码不存在";
}
这样在实际项目中,错误信息可以写到配置文件里,系统启动后将配置文件的内容读到结构体数组arr中即可。这样如果增加/删除/修改错误信息就可以直接在配置文件中改,无需改变代码。
面试过后,我试问自己,我是真的不会吗?是没有见过这样的需求也没有好好理解面试官的题意罢了。不过菜是原罪,错了就要承认,不会就要学习,跌倒了就要爬起来。
简单总结一下,在目前看来,刷算法题和设计题仍然是最重要的,因为这是面试官考察应届候选人水平的唯一不变的标杆,但是不要让刷题思维限制了你的项目思维;实力固然重要,运气在面试中也很重要(如果没有强到一定水平的话);有些面试官不会问基础知识因为觉得是在背书无法考察候选人的真实水平,但是有些面试官就喜欢问基础因为基础不牢地动山摇;逻辑题也会偶尔拿进来考察一下候选人的智商;面试官会有意无意地考察候选人面向对象的思想。
体验完了两轮面试,给我的感觉就是腾讯的面试很全面很科学,既考察知识范围的广度又考察编程内功。
相信在经历风雨后,我们都能成为想成为的自己。
热 文 推 荐
你点的每个“在看”,我都认真当成了喜欢