首先感谢热心助人的崔同窗,耐心给我讲解猿题库的面试风格,让我能安心只准备了算法和system design。不过算法也没准备,最近正常刷题而已;system design也只是复习了下搜狗的项目。至关因而裸面了。。。万幸其余方面一点都没有问,最后也拿到了实习offer。前端
一面的面试官看起来不到30,应该是普通研发,固然极可能是准mentor。进屋介绍了下面试流程和公司,而后让我问问题。我以为过不过都没准呢有什么好问的,因此接话茬问了几个面试流程相关的问题。java
都说猿题库主要考算法,我觉得是暴力的上来就写题,这面试官不错,先让我自我介绍放松了一下,而后就说,“咱们写个题吧”。git
给定一个单链表的头结点head,两个值v一、v2,在链表中找到这两个结点并交换。github
跟面试官肯定的信息:面试
题目很简单,那极可能在考察边界条件和coding style;算法方面cc大神说的好,链表靠画图;交换数组须要记录前置节点,给头结点增长前置节点dummyNode,简化代码,也简化边界条件。算法
刚要写,面试官就拦住我,跟我说能够先聊聊思路,能先动嘴皮我万分感谢啊,,,“边界条件先无论,算法的主要过程是……”。面试官确定以后才让我写代码。数据库
算法很简单,直接看代码吧:数组
// define of Node(val, next)
public Node swap(Node head, int v1, int v2) {
// no need to swap
if (head == null || head.next == null) {
return head;
}
// no need to swap
if (v1 == v2) {
return head;
}
Node dummy = new Node(0, head);
Node prev1 = dummy;
Node prev2 = dummy;
Node prev = dummy;
while (prev.next != null) {
if (prev.next.val == v1) {
prev1 = prev;
}
if (prev.next.val == v2) {
prev2 = prev;
}
}
// no need to swap
if (prev1 == dummmy || prev2 == dummmy) {
return head;
}
internalSwap(prev1, prev2);
return dummy.next;
}
private void internalSwap(Node prev1, Node prev2) {
if (prev1.next == prev2) {
internalSwapNextTwo(prev1);
return;
}
if (prev2.next == prev1) {
internalSwapNextTwo(prev2);
return;
}
Node next1 = prev1.next;
Node next2 = prev2.next;
prev1.next = next2;
prev2.next = next1;
Node tmp = next1.next;
next1.next = next2.next;
next2.next = tmp;
return;
}
private void internalSwapNextTwo(Node first) {
Node second = first.next;
Node third = second.next;
second.next = third.next;
third.next = second;
first.next = third;
return;
}复制代码
写完代码,面试也是先让我拿着代码讲讲思路。这部分主要是免去面试官硬读代码的麻烦,也方便面试官考察你代码的模块性,甚至一些变量、函数的命名。算法主体刚才讲过,因而我一句话带过,重点讲了swap方法中的边界条件,而后面试官就开始本身看代码。以后又让我讲internalSwap方法的原理,我也是先讲算法主体,再讲边界条件。缓存
面试官说这个题很常见,可是我只据说过,,,后悔本身当时没看。bash
给定一个
n*n
的矩阵,元素为整数,将矩阵旋转。
跟面试官肯定的信息:
我只知道有矩阵旋转这道题,因此刚听完题目的我是懵逼的。那没办法,只能硬着头皮上。
这道题我开始没沟通好,没有问返回值,觉得只要按照旋转后的顺序输出矩阵元素便可(控制循环顺序)。说完思路,面试官意识到我理解错了题意,耐心告诉我要返回一个矩阵。但也没有急着否认个人方法,而是问我这种方法的时间复杂度和空间复杂度。那就都是O(n^2)了,面试官就说也能够改变原来的矩阵,下降空间复杂度。
我想了一会,想到reverse操做通常是O(1)的,并且常常能经过各类reverse的组合达到神奇的效果。因而就想着先按照斜主对角线reverse一下,也就是斜翻——还差点;再按照竖中轴线reverse一下,也就是对翻,握草正好搞定。
我用3*3
的矩阵跟面试官说了思路后,面试官又让我实验4*4
的矩阵,,,我觉得有问题,结果画完发现是正确的。面试官就说,“恩,实验了也对吧。这实际上是个数学问题,跟矩阵的性质有关,你能不能证实一下?”我大学线代课都是睡过去的,跟面试管坦诚线代真的忘光了,因而就直接让我写代码了。
代码:
public int[][] transfer(int[][] matrix) {
if (matrix == null || matrix.length <= 1) {
return matrix;
}
if (matrix[0] == null || matrix[0].length <= 1) {
return matrix;
}
xiefan(matrix);
duifan(matrix);
return matrix;
}
private void xiefan(int[][] matrix) {
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < i; j++) {
swap(matrix, i, j, j, i);
}
}
}
private void duifan(int[][] matrix) {
for (int i = 0; i < matrix.length; i++) {
for (int j = 0; j < matrix[0].length / 2; j++) {
swap(matrix, i, j, i, matrix[0].length - 1 - j);
}
}
}
private swap(int[][] matrix, int x1, int y1, int x2, int y2) {
int tmp = matrix[x1][y1];
matrix[x1][y1] = matrix[x2][y2];
matrix[x2][y2] = matrix[x1][y1];
}复制代码
后面的过程跟上一题同样,分析时间复杂度与空间复杂度等。
网上更流行的解法是直接旋转一圈,我面试时也想到了,不过以为很差推导,没有reverse的性质通用。不过我以为须要掌握这种用法,也不用硬记,看过一遍key point,面试时很快能推出来。参考LeetCode:Rotate Image的解法2。
两道算法题以后,时间就差很少了,面试官让我介绍下我在搜狗最满意的一个项目,我回答的很是乱,还好此次主要不是面试system design。而后面试官让我等一下二面就走了,我赶忙复习以前整理的项目介绍。
二面的面试官,,,确实很帅,让我想起了去年9月份在nice的面试,不过面到一半去开会了,让我等20分钟,,,结果我等了一个小时。中间还有更尴尬的事情,不过这不重要,也不是人家故意的,面试最重要。
原本是先考system design,再考算法,不过system design考到一半面试官就去开会了,丢给我一道算法题,开完会也是先讲的算法题,因此这里先介绍算法部分。
只考了一道算法题,并且面试官说完题意,看我没有思路就先走了,留给我20分钟思考+代码。
知道归并排序吧?归并排序通常用递归实现,若是不用递归怎么实现呢?
跟面试官肯定的信息:
对,才开始刷题的我也没见过这道题。可是过了一会也想通了,就是用循环模拟递归版归并排序的执行过程,算法描述以下:
由于面试官出去开会了,因此我直接写了代码:
public int[] mergeSort(int[] array) {
if (array == null || array.length <= 1) {
return array;
}
int[] arrA = array;
int[] arrB = new int[arrA.length];
for (int seg = 1; seg < arrA.length; seg *= 2) {
for (int i = 0; (i + 1) * seg < arrA.length; i++) {
merge(arrA, i * seg, (i + 1) * seg, seg, arrB);
}
System.arrayCopy(arrB, arrA);
}
return arrA;
}
private void merge(int[] arrA, int start1, int start2, int seg, int[] arrB){
int l = start1;
int r = start2;
int i = start1;
while (l < start1 + seg && r < start2 + seg
&& l < arrA.length && r < arrA.length) (
if (arrA[l] <= arrA[r]) {
arrB[i] = arrA[l];
l++;
} else {
arrB[i] = arrA[r];
r++;
}
i++;
)
while (l < start1 + seg && l < arrA.length) {
arrB[i] = arrA[l];
l++;
i++;
}
while (r < start2 + seg && r < arrA.length) {
arrB[i] = arrA[r];
r++;
i++;
}
return;
}复制代码
后面也是分析时间复杂度和空间复杂度等。注意如何分析这道题的时间复杂度:外层循环O(logn),内层循环O(n),数组拷贝O(n),因此算法总体是O(nlogn)。
背景不表,精简描述以下:
学生天天都会作题,要求设计一个架构,学生作题后,能实时看到本身的排名、前100名的排行榜,做业量天天更新。
跟面试官肯定的信息:
有一些system design的题目很经典,在面试中常常出现。我不知道这种属于经典题仍是面试官本身出的题目,反正我都没见过,也没准备,作起来是同样的。不过若是有精力,建议多看看经典案例,真的能学到很多key point。
题目归纳起来有四个要求:
第4点没什么意思,分库分表,甚至天天清空一次数据库均可以,能够不考虑。对于高并发的系统而言,可归纳为两个需求:
我设计的方案以一个桶为核心——之因此想到桶,多半是由于面试前还在复习ConcurrentHashMap的源码。ConcurrentHashMap是java.util.concurrent包中的一个神器,也算是面试常考点,你知道它的size()方法是怎么实现的吗?我忘记了,不过我在书里的笔记讨论了三种方案:
好好理解这三种方案,个人设计基于方案2.
对于需求1,至关于ConcurrentHashMap中调用size()方法的操做。不一样的是,可能存在上千万个学生,对每一个学生都维护size显然是不合适的,而不须要维护size的就只有方案2了。
具体来讲,能够认为做业量有上限,一个学生不可能一天作无数做业。则能够维护一组有限的有序桶(桶内是否有序可根据读写比例调整),每一个桶表明一段做业量的范围(桶的范围可根据并发规模设置,做业量频率高的桶可继续分红更小的桶,做业频率低的桶可合并成更大的桶),桶之间不重合。则:
当前学生的排名 = 当前学生以前全部桶的size之和 + 当前学生在其桶内的排名复制代码
需求1的实时性要求通常没有那么高,由于用户只看本身的排名,就算有必定延迟也不会影响用户体验,所以,每次用户查看排名都计算一次的消耗是能够接受的。固然,咱们能够进一步优化,好比维护截止到每段开头的总排名R,不过这须要增长一个服务实时去维护该段以后段的总排名R',极可能是得不偿失的。
称做业量高的桶为大端桶,做业量低的桶和小端桶。对于需求2,至关于要维护大端桶中的前100个学生。因为假定做业量是有限的,则能够从最大做业量所在的桶开始遍历,直到遍历非空桶,且已按照做业量递减的顺序遍历了100个学生为止,返回排行榜。
能够作一个优化——记录当前最大做业量maxCnt,则最大桶的上界不超过maxCnt,且最大桶的size也不超过阈值sizeThreshold,那么每次可直接从最大的空桶开始遍历。
能够继续优化——因为咱们关心的是固定前100个学生,那么直接维护一个最大堆maxHeap是更好的选择。对于排行榜而言,显然只有插入和查询(取前100)操做,baseline模型至关于插入O(1)查询O(nlogn);或者插入排序,则插入时O(n),查询时O(1);而最大堆插入时O(logn),查询时O(1)。
因为做业量高的学生老是极少数的,因此大端桶的并发量要远小于其余桶,维护maxCnt和maxHeap的成本很是小,收益却至关可观。
最后,能够在maxHeap前加一层缓存,异步更新,以加速前端访问。
在基本的架构介绍完后,面试官又给出了几个follow up问题:
对于问题1,我把Hadoop的HA方案套上了。面试官彷佛不了解Hadoop的内容,以为我答的不错。对于问题2,我清楚分析了不该该把桶与服务保存在同一分内存中,而应该尽可能让服务无状态,桶交给存储层,不须要关心桶的结构。把问题2转换成了问题3,“如何解决存储的单点故障”。
首先,HA的方案仍然有效。但老一套没意思,我就说能够换一种方案。服务端多实例,客户端挂载服务端的实例列表,写数据时让客户端维护服务端的数据一致性(所有写才算写成功),还顺带解决了读数据时负载均衡的问题;用事务id解决客户端写失败致使的一致性问题。
如今写面经的时候才发觉这里回答的并很差。这种方案的写负载过高了,而并发写时每每涉及同步问题,就更麻烦了。若是仍是基于客户端挂载的方案,那么能够参照NRW策略,只须要保证R+W>N,就能够保证强一致性。不过总感受仍是不够好,好比这种设计没有考虑到可伸缩性,距离真正的分布式存储系统差距还很是大。
PS:
- N表明数据所具备的副本数。
- R表示完成读操做所须要读取的最小副本数,即一次读操做所须要参与的最小节点数目。
- W表示完成写操做所须要写入的最小副本数,即一次写操做所须要参与的最小节点数目。
哎,面试完已经7点半了。从下午4点半开始,3个小时,仍是挺熬人的,累累累,不过当场拿到offer十分知足。
最后询问面试官对个人评价:
二面的面试官是部门总监(创业公司的总监都至关年轻),这么评价,程度上确定有夸张了。不过方向上应该值得参考,帮助本身扬长避短。
我还询问了今天面试跟正式校招的难度差距。面试官说跟校招难度相似,略微低一些,但差距不大。我挺惊讶的,都说猿题库面试重算法,比较难,面试以前我觉得本身一道题就会被轰走,没想到撑到了最后。。。第一场面试经历如此甜美,幸福幸福~
给本身的建议:
本文连接:【面经】猿题库-2017年8月25日,散招实习生
做者:猴子007
出处:monkeysayhi.github.io
本文基于 知识共享署名-相同方式共享 4.0 国际许可协议发布,欢迎转载,演绎或用于商业目的,可是必须保留本文的署名及连接。