【面經】猿題庫-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 國際許可協議發佈,歡迎轉載,演繹或用於商業目的,可是必須保留本文的署名及連接。

相關文章
相關標籤/搜索