【面经】猿题库-2017年8月25日,散招实习生

首先感谢热心助人的崔同窗,耐心给我讲解猿题库的面试风格,让我能安心只准备了算法和system design。不过算法也没准备,最近正常刷题而已;system design也只是复习了下搜狗的项目。至关因而裸面了。。。万幸其余方面一点都没有问,最后也拿到了实习offer。前端

一面

一面的面试官看起来不到30,应该是普通研发,固然极可能是准mentor。进屋介绍了下面试流程和公司,而后让我问问题。我以为过不过都没准呢有什么好问的,因此接话茬问了几个面试流程相关的问题。java

算法

都说猿题库主要考算法,我觉得是暴力的上来就写题,这面试官不错,先让我自我介绍放松了一下,而后就说,“咱们写个题吧”。git

链表,交换两元素

给定一个单链表的头结点head,两个值v一、v2,在链表中找到这两个结点并交换。github

跟面试官肯定的信息:面试

  • 有无prev
  • Node的结构(val,next)
  • 返回值

题目很简单,那极可能在考察边界条件和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分钟思考+代码。

知道归并排序吧?归并排序通常用递归实现,若是不用递归怎么实现呢?

跟面试官肯定的信息:

  • 返回值

对,才开始刷题的我也没见过这道题。可是过了一会也想通了,就是用循环模拟递归版归并排序的执行过程,算法描述以下:

  1. 申请数组B,大小为A.length
  2. 设segLen为1,定义长度为segLen的子数组为1个seg
  3. 每两个seg为一组,分别作2路归并
  4. 将数组B拷贝回A
  5. segLen = segLen * 2
  6. 若是segLen小于A.length,则回到步骤3;不然继续
  7. 返回数组A

由于面试官出去开会了,因此我直接写了代码:

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)。

system design

背景不表,精简描述以下:

学生天天都会作题,要求设计一个架构,学生作题后,能实时看到本身的排名、前100名的排行榜,做业量天天更新。

跟面试官肯定的信息:

  • 排行榜按照什么排序——做业量
  • 做业量是计算做业次数仍是总量——总量

有一些system design的题目很经典,在面试中常常出现。我不知道这种属于经典题仍是面试官本身出的题目,反正我都没见过,也没准备,作起来是同样的。不过若是有精力,建议多看看经典案例,真的能学到很多key point。

题目归纳起来有四个要求:

  1. 全部查询都是实时的
  2. 学生能查看本身的排名
  3. 维护排名前100的排行榜
  4. 做业量天天清零,重新计算

第4点没什么意思,分库分表,甚至天天清空一次数据库均可以,能够不考虑。对于高并发的系统而言,可归纳为两个需求:

  1. 学生能实时查询本身的排名
  2. 排行榜能实时更新

我设计的方案以一个桶为核心——之因此想到桶,多半是由于面试前还在复习ConcurrentHashMap的源码。ConcurrentHashMap是java.util.concurrent包中的一个神器,也算是面试常考点,你知道它的size()方法是怎么实现的吗?我忘记了,不过我在书里的笔记讨论了三种方案:

  1. 维护size变量;每次有段更新时,同步修改size变量。并发瓶颈在于size变量上的锁,至关于退化回了串行更新。
  2. 不维护size变量,但维护每段的segSize;每次有段更新时,仅修改segSize;每次调用size()方法,把全部segSize加起来。可根据一致性要求选择不一样的segSize加和策略:要求彻底一致性就在计算时所处全部seg,那么调用size()方法时产生了并发瓶颈;要求弱一致性,可直接加和。
  3. 维护size变量;当segSize修改时,将size置为-1;调用size()方法时,若是size为-1,则从新计算size,同上;若是size不为-1,则表示期间未修改seg,直接返回size。方案3主要针对方案2,至关于缓存了size值,这样不须要在未修改segSize时重复计算size。

好好理解这三种方案,个人设计基于方案2.

需求1

对于需求1,至关于ConcurrentHashMap中调用size()方法的操做。不一样的是,可能存在上千万个学生,对每一个学生都维护size显然是不合适的,而不须要维护size的就只有方案2了。

具体来讲,能够认为做业量有上限,一个学生不可能一天作无数做业。则能够维护一组有限的有序桶(桶内是否有序可根据读写比例调整),每一个桶表明一段做业量的范围(桶的范围可根据并发规模设置,做业量频率高的桶可继续分红更小的桶,做业频率低的桶可合并成更大的桶),桶之间不重合。则:

当前学生的排名 = 当前学生以前全部桶的size之和 + 当前学生在其桶内的排名复制代码

需求1的实时性要求通常没有那么高,由于用户只看本身的排名,就算有必定延迟也不会影响用户体验,所以,每次用户查看排名都计算一次的消耗是能够接受的。固然,咱们能够进一步优化,好比维护截止到每段开头的总排名R,不过这须要增长一个服务实时去维护该段以后段的总排名R',极可能是得不偿失的。

需求2

称做业量高的桶为大端桶,做业量低的桶和小端桶。对于需求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

在基本的架构介绍完后,面试官又给出了几个follow up问题:

  1. 如今的服务都是单点的,如何解决单点故障?
  2. 你的桶要保存在内存里,发生了单点故障怎么办?

对于问题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 国际许可协议发布,欢迎转载,演绎或用于商业目的,可是必须保留本文的署名及连接。

相关文章
相关标签/搜索