微信在校生实习面试小记(iOS端)

周二晚上上课的时候突然接到微信的面试通知,于是大晚上的赶忙啃了啃算法设计和数据结构,还将自己一年来在iOS客户端上开发所学会的技术罗列了一下,就兴致勃勃的准备面试了。趁着现在记忆新鲜,赶紧记录一下昨天的面试过程。


面试地点是广州客村的T.I.T创意园的腾讯楼,和我之前预想的不同(毕竟是第一次面的小白),这里并没有西装革履,透着玻璃看到的里面是一个个和我一样的程序员,面对着两三个屏幕,有的奋“笔”疾书,有的忙里偷闲。 但看到这么多人,写着我一年来一直在学习的东西,这种感觉真的很好。

面试官是一个25岁左右的开发人员,在简单的看了看我的简历之后,他问我是否熟悉C、C++,我答道可以运用C++进行编程;他又问道,你认为栈和堆的区别在哪里,我对堆没有了解,就简单的说了一下调用栈的工作流程,随后面试官就让我进行一个当场的笔试,3道题,45分钟作答,考的都是非常基础的数据结构、算法知识,分别是字符串比较、链表反转,和一个二叉树遍历的简单应用,要求用C/C++来编写。如果说学习过ACM课程,或者是平时算法、数据结构课程比较用心的话,这几道笔试题解决起来都没有什么难度,但是事实证明,除了算法的正确性以外,面试官(或者说是微信)更注重的是细节和效率。

一题一题说,

第一题,字符串比较,比较两个输入的字符串是否由相同字母组成,如show和whos由同样的字母组成。其中字符串以 char * 的方式存储。

显然先将字符串排序再逐位比较是效率偏低的算法,我就采用了标记数组的方式,int *flag = new int [26*2]; ,初始全部置0,以strA,strB表示两个字符串,我先用一个for遍历strA,根据 str[i] - 'a' 和 str[i] - 'A' 判断完大小写之后,对应的flag[ str[i] - 'a' ] 或者 flag[ str[i] - 'A' +26 ] 自增 ; 遍历strB的时候,方法相似,将自增改为自减。最后遍历一次flag数组,数组中所有数值均为0,则返回true,否则返回false。

面试官看完第一题后,首先揪出了我程序尾 flag 数组没有 delete的遗漏(ARC用多了+对内存堆机制不了解啊!!),随后认为我的flag数组可以改为每单位占一字节的char类型,(毕竟实际情况下一个单词中单个字母出现的次数不太可能达到2^8 - 1以上),并顺便问了我一些基本数据类型的大小,另外,由于c++数组是没有array.length()或是array.count这样的标识数组长度的函数/变量,所以for循环时,我是用 sizeof(str)/sizeof(char) 来判断循环上界,于是面试官就又问我,char strA[250] ;  char *strB,对这两个使用sizeof函数,输出结果会是怎么个情况,结果又因为对sizeof理解不深,这个问题根本答不上来( sizeof 比较具体的解释,参考:http://blog.csdn.net/candyliuxj/article/details/6307814 , candyliuxj的CSDN博客 ),于是就这样战战兢兢的结束了第一题的“审核”。

第二题,链表反转,就是一条单链表,给Node *head指针给你,叫你把链表逆置过来,最后让head指向新的表头。

现在网上较多的算法有两种:一种是保存当前的结点为current(初始状态 current = head) , 当 current -> next != NULL时,next = current->next ; current -> next = next->next; next->next = head ; head = next ,即不断向后遍历并进行断链 -- 头插入直到原始的head结点变成尾结点,另一种是设置三个临时变量 pre,current,next, 初始时 pre = head, current = head->next ,  当 current != NULL && current->next != NULL时, 做 current -> next = pre ; pre = current ; current = next ; next = current -> next ,  最后循环结束,还差一次反转,current -> next = pre; 再重置原来的头结点即可 head -> next = NULL ; head = current . 但是当时做题时,我担心自己被指针和起始、终止两个临界状态搞晕,导致算法错误,选择了一个思路比较清晰,但是比较耗费空间的做法:开一个stack,一股脑把整个链表塞进去,塞完之后,再把栈里的结点拿出来成链就可以了。

  面试官看完之后,问了问我说,我这个算法会不会出现死循环,我说我觉得应该不会,于是他给我提出了个这种情况:


微信在校生实习面试小记(iOS端)_第1张图片


也就是循环链表的情况,问我如何在不改变表结点结构(只有value 和一个next 指针)的前提下,判断这种情况并报错,而不是导致程序进入死循环。我的第一个想法是开一个visited数组,访问过的结点就做个标记,每次访问前查询标记数组,若已访问过,说明链表存在环,则可以进行错误处理,但由于无法知道链表的长度,因此标记数组的长度也无法确定,而且由于结点value的可重复性,只能以每个节点的内存空间地址作为标识符,当数据量大起来的时候,搜索标记数组的开销也会非常的大。中间还想到利用图论的知识,n个点的连通图至多只有n-1条边,若超过n-1条边,则说明图中有环,程序就可以报错,但其实这个想法也是不对的,因为无法知道结点个数。最后面试官也没有给我答案,最后回到学校和同学讨论,想出一个“偏方”,那就是新建一个带访问标记flag的链表结点,再仿造现有链表成链,则可以在出现环的时候通过flag标记检测得到。但较为“正统”的做法是利用Floyd判圈算法,具体做法参考ljd4305的CSDN博客:http://blog.csdn.net/ljd4305/article/details/19572423


第三题是一道二叉树的简单应用,问的是节点带value的一颗二叉树,对于给定的一个val值,是否存在从根节点到叶子节点的一条路径,使得所有节点的value的和等于val,这题基本没有太大难度,简单的记录好当前值currentVal,判断好叶子节点的结束情况,并在非叶子节点做好剪枝工作,写一个简单的递归深度遍历算法就可以得出结果了,这道题面试官也没有多说。


接下来,面试官会根据你的简历、项目经验,来有针对性的提出一些平台相关的问题,我比较拿的出手的是在学校学院做的一个项目,大致内容是,老师利用iPad,整合文字、图片、视频资源后,我的程序将他们按一定规则放置到指定目录,生成html文件,配合前端的js,css进行排版,最后生成一个html电子书,并支持上传至FTP服务器功能。首先面试官让我描述了一下程序的功能,然后让我说一下这个项目的难点。

其实从技术的角度来说,程序没有非常难的地方,无非是电子书被修改时,要找准html中相应的位置,并删除当前页的旧的视频、图片资源,更新好新的资源,所以当我说生成html的时候稍有难度的时候,面试官的回应是:“不就是用oc生成html而已吗” 。其次程序比较有看点的地方就是支持本地上传功能,上传的过程大致是:整合收集待上传书本的书本信息,生成JSON数据,更新云端的书本信息,更新成功后,再将现有的书本的整个文件结构进行压缩上传,整个过程均在后台完成,用到了dispatch_apply,dispatch_group等等GCD接口,而上传下载主要是利用NSInputStream和NSOutputStream完成数据读写。由于后台线程操作多且杂,我没有把重点放在我怎么怎么控制线程,怎么怎么保证线程安全等等,而是解释了一下NSStream,面试官先是问我,当inputStream读取速度快于outputStream向服务器写入速度时怎么办,我说设置一个缓冲区,由于input读入和output输出都会返回实际读入和实际写出的字节数,所以我只用保证每次读入后,output输出的字节数等于读入的字节数就可以了,当output单次输出小于单次读入时,则不读入,让output继续输出直至输出完毕。随后,面试官又问我,为什么NSStream支持直接将数据写到FTP云端,问我NSOutputStream内置支持FTP协议的机制是什么,我又答不上来了,而NSStream不支持断点续传,NSURLSession则可以,但NSURLSession仅支持HTTP和HTTPS协议,所以面试官进一步问了FTP,HTTP,HTTPS三个协议的异同,又问了我NSURLSession断点续传的原理,我无一答得上来 .... 后来面试官又问了一些Cocoa中类的内存管理问题,主要就是引用计数,强弱指针的问题,没有再深入。


到这里面试基本就结束了,我最后问了问面试官,他觉得我缺点在哪里,他说我好奇心不够,编码的时候仅仅是学会怎么做,但不好奇它背后的运行机制,譬如NSString *aString = @“This is a string”; 生成了一个NSString类对象,那么背后内存是如何申请的?这片内存是如何管理的?当使用NSOutputStream上传的时候,调用它的接口设置服务器访问用户名、密码的时候,这个类对象做了些什么?为什么这个类对象会内建支持FTP协议?我利用这个类访问服务器的时候用户名密码是否会给人窃取?C中new出来的空间放在哪、如何管理?为何会有内存泄露的产生等等 .. 


总结一下,我觉得就微信来说,他们的团队更加注重算法的效率、空间(标记数组利用char * 而不用int *)、健壮性(循环链表判错),更加注重你对内存管理的理解以及对CocoaTouch API的理解,而不是简单的使用,毕竟现阶段软件行业也发展到了一个非常成熟的阶段,仅仅会用类库、会写App的人已经不足以打动他们了;但同时,微信团队不太注重代码的规范性、项目的体系架构,我在简历中花了相当一部分篇幅描述我学习了MVVM、VIPER等架构用来弥补MVC架构的不足,但面试过程中面试官并没有提及任何有关于职责分离、代码复用的话题,也许是因为现在微信的更新迭代非常的快,用户的需求增长也很可怕,所以为了满足用户,微信必须要高速地开发出可靠的产品,而对代码的规整没有太大的要求。

而于自己,通过这一次面试我也了解到,学校开设的课程仅仅是一个入门,带你走进了IT界的大门,给了你一盏灯(C/C++),一双鞋(数据结构、算法),但也仅此而已,而企业需要的远不止这些,而这些知识、这些专业技能,学校课程是给不了的,必须要自己一步一步地走,一点一点地学,而且不能局限于会用,而应该尽量理解为什么应该这样用,某些方法能做什么,为什么它能做到,而其他方法不能,同时也更应该强化对计算机工作原理的了解,内存分配管理、网络协议等等。

而关于面试经验,首先要建立在自身有一定技术优势,自己的项目有一定卖点的情况下,在和面试官交流的过程中,引导他去问你解决了的技术难题,避开自己的知识盲区,扬长而避短。

这是我人生中的第一个面试(抛开社团面试不说),我觉得最重要的还是自身的实力,当然若能有一些面试的技巧更好,程序员的路还很长,希望能和点进来看的读者共勉,共同进步。

你可能感兴趣的:(心得,微信,面试)