微軟高質量面經

第一題是按序輸出格雷碼。輸入的參數是格雷碼長度N。python

剛上來可能緊張,沒什麼思路。面試官問了我一下,就提示我是否是能夠從前一個序列中找到規律。我一會兒反應過來,就跟他說了個人想法。ios

通常在交流後若是以爲你的思路沒問題,就開始在白板上寫代碼。要求是能運行的代碼。git

第二題是最長遞增子序列。Longest Increasing Subsequence - LeetCode
我首先想到動態規劃方法,就跟面試官說了一下。他說能夠的,你想一下方程吧。
想好後和他交流了一下,此次沒讓我寫代碼,但讓我繼續優化。不過此次始終沒想起來。

面試

 

第一題:  格雷碼算法

第二題:最長遞增子序列 (動態規劃O(n^2), 二分優化到O(nlogn)數組



也就幾分鐘以後開始作題。此次題目是求二叉樹中任意兩點造成的路徑上節點(每一個節點有個值)之和的最大值。(Leetcode 124)
我想到方法後也是先跟他確認,而後開始寫代碼。寫的時候我還問他節點值是否可能爲負。
寫好代碼以後他會仔細看代碼。(一面面試官也很仔細)幾乎是一行一行地讀。
此次讀代碼解釋代碼的時間比較久。就沒有第二題。




編輯器



三面應該是個Leader?也是先聊天,還說以前去南大宣講我記不記得他。哈哈,我說你這麼一說我纔想起來。
他就空着手過來,不像一二面面試官(順帶一提,二面面試官用的 MacBook + macOS)。
就閒聊了幾句,我就記得問我以爲本身的優點是什麼。而後問了前兩面問了啥題,而後開始作題。
此次這個題目羅裏吧嗦的:
有三個轉盤(A-Z),處於某一個狀態start,好比MSF。求出將start轉到另外一個狀態target所要轉動轉盤而造成的字符串序列。好比MSF->OFC:[MSF,NSF,OSF,OSE,...,OFC。此外,有另外一個參數BlockList,是個字符串列表,輸出的序列不能包含BlockList中的字符串,即轉動時不能通過這些狀態。
問我有沒有問題他就出去了,我就開始寫,我就組合了一個BFS和遞歸寫了一個,有點亂,白板上也很差改。沒過多久他就回來了。開始也像前兩面同樣逐行讀代碼,解釋。我也不知道對不對🤦‍♂️。看他的表情也不知道對不對。
不過此次由於快到下班時間了,就很快結束了。







函數


 

一面

第一個題是「Z字形打印二叉樹」,給定一顆樹,先打印根節點,從第二行開始先從左到右,再從右到左打印。第一次面試真的有點緊張,剛開始敲鍵盤的時候手都在抖。這個題實際上是《劍指offer》上比較經典的題目了,還好我以前刷了一遍。因此在草稿紙上簡單畫了一下,就思路就比較清晰,給定兩個棧,分別交替存儲每層的節點。給面試講了一下大體思路以後,就開始寫代碼了,爲避免沉默的尷尬,一邊寫一邊給面試官講具體的思路。具體的代碼能夠參考牛客網-劍指offer-z字形打印二叉樹,這裏就不寫了。測試

作完以後面試官讓我計算一下複雜度,由於每一個元素分別入棧和出棧一次,時間複雜度是O(N)。棧裏存儲的元素也不會超過N,因此空間複雜度也是O(N)。複雜度的計算感受要求不是很嚴格,給出答案後面試官就沒說什麼,而後繼續下一題。優化

 

BFS遍歷題目

 


 

第二題,圖相關,找到最大的好友圈。給定N個club,每一個club裏有人員的ID,找出其中最大的好友圈。什麼算好友圈呢, 舉個例子比較容易理解:

有3個club,其中每一個club擁有的會員以下:

clubI: [1, 2, 3]

clubII: [2, 3, 4]

clubIII: [5, 6, 7]

由於2和3都屬於club1和club2,因此1,2,3,4都屬於同一個圈子。最大的好友圈數量就是4。

看到這個題的第一反應是深度優先遍歷(DFS),要作DFS的話,確定得用字典,每一個key對應一個list,而後遍歷就行了。可是怎麼設計key和list,還沒想好。由於不想讓面試陷入太長的尷尬期,就決定開始寫。最初的想法是以人員ID做爲key,club做爲list,遍歷每一個人擁有的club id,再根據該id遍歷該club下的人數,累加起來。但後來發現這種思路的複雜度過高,寫起來也比較麻煩,大概寫到一半後,面試以爲這樣複雜度過高了,讓我先停一下,讓我算算這樣作的複雜度,我估計了一下,沒想好,反正就是很高。面試官讓我再換個思路,想個簡單一點的,我忽然腦洞一開,以爲,我把他們每一個club當個集合,求下並集和交集不就好了嗎?若是兩個集合有交集,那就合併,沒有交集就不合並,代碼很簡潔,感受幾行就能寫完。忽然爲本身的腦洞鼓掌....可是面試官又問,這樣作的時間複雜度呢?最差的狀況每兩兩集合作交集,這一步複雜度爲O(N^2),再求並集,至少O(N^3)。O(N^3)實在過高了,面試官又讓我想一想簡單一點的,可是時間快到了,而後面試官就直接開始指引我了,把每一個人的ID看成圖的節點,用領接表來表示從一個點到另外一個點可達,已經遍歷了的點就標記一下,直到全部的點都被標記。面試官最後讓我說這個算法的複雜度,我算了算,O(N),確實好多了,代碼也不用寫了。

最後再問了問面試官部門的業務,而後就結束了。從頭至尾面試官都挺和善的,感受很不錯。

 

應該是並查集題目:

 

 

 

二面

一面和二面是沒有關係的,反正就是換我的來作題。其實個人二面應該是一面,但由於第一天把他放鴿子了,因此HR在次日從新安排了面試。

感受比上一個面試官要嚴肅一點,一來就直接作題,還說,「咱們搞快點,儘可能在45分鐘內結束。」(HR定的是一個小時)。第一個徹底沒見過,叫格雷碼(Gray Code,後來發現leetcode上有原題),傳入一個N,要求返回第N行的格雷碼。格雷碼是這樣的:

2: 00 01 11 10

3: 000 001 011 010 110 111 101 100

4: 0000 0001 0011 0010 0110 0111 0101 0100 1100 1101 1111 1110 1010 1011 1001 1000

他和二進制很相像,可是每次只變化一位。

徹底沒見過這種題(刷的題比較少),真的有點緊張,一開始想從二進制入手,但想了想沒思路,因而就想單純的找規律。看了一會,終於看出規律來了,好比3是由在2的格雷碼前面先加個0,再把2反轉一下加個1,提煉出來就是G(N + 1) = (0 + G(N)) +(1 + R(G(N)),R表示反轉列表。發現規律後就很好寫代碼了。爲了方便,這題直接用python。

first = ["00", "01", "11", "10"]
    dp = {}
    dp[2] = first
    def grayCode(n):
        if n in dp:
            return dp[n]
        else:
            temp = list(map(lambda x: "0" + x, grayCode(n - 1))) + 
                        list(map(lambda x: "1" + x, grayCode(n - 1).reverse()))
            dp[n] = temp
            return dp[n]

以後照例面試官讓我講一下複雜度,若是以N表示位數的話,時間複雜度就是O(N * 2 ^ N),由於反轉字符串須要花費2^N,空間複雜度是O(2^N)。這個時間複雜度挺嚇人的,可是面試官也沒說啥,就繼續下一個問題。

因爲時間問題,第二個問題面試官沒有讓我寫代碼了,只是說下思路和計算複雜度就行。問題是,給定一個整數數組,求子數組的數量,要求子數組的元素個數大於等於2,且順序不能變。好比[1,2,3,4,5], [1, 2]或[1,3]是他的子數組,可是[2,1]不是。因此不能是簡單的排列組合。

個人思路是等差數列的求和,

好比個數爲2的時候,計算公式: F(2) = (n - 1) + (n - 2) + (n - 3) + ... +2 + 1 = (n / 2) * (n - 1)

個數爲3的時候, 遞歸,取出第一個元素後,對剩下的子數組又重複上面的計算。

這裏感受不是很嚴謹,尚未仔細驗證,面試官就開始問下一個問題,求全部子數組中的遞增子數組中長度最長的數組。

這裏想了好久,沒有想好,由於面試官先讓我算子數組的個數,我潛意識裏就覺得是已經獲得了全部子數組,要比較他們的長度和是否遞增。最終仍是沒有想到解決方案,面試官說沒事,面試就這樣吧,語氣也很友好。到面試快結束的時候才反應過來,其實不必管全部子數組,只須要在原數組中查找最長的遞增子數組就行了,可是具體怎麼作也沒思路,而後面試就結束了。

後來在leetcode上查了一下,有一個用動態規劃的o(N^2)的解法,有個只須要O(NlogN)的解法要用到線段樹,就直接放棄了,這裏只貼下DP的解法吧:

class Solution(object):
    def findNumberOfLIS(self, nums):
        N = len(nums)
        if N <= 1: return N
        lengths = [0] * N #lengths[i] = longest ending in nums[i]
        counts = [1] * N #count[i] = number of longest ending in nums[i]

        for j, num in enumerate(nums):
            for i in xrange(j):
                if nums[i] < nums[j]:
                    if lengths[i] >= lengths[j]:
                        lengths[j] = 1 + lengths[i]
                        counts[j] = counts[i]
                    elif lengths[i] + 1 == lengths[j]:
                        counts[j] += counts[i]

        longest = max(lengths)
        return sum(c for i, c in enumerate(counts) if lengths[i] == longest)

 

 

三面

一二面結束以後就收到了三面的邀請,過了一天就開始三面。網上查的狀況大可能是三面只作一個題,結果我仍是作了兩個題。由於終面,面試官是個Leader,感受獲得他頗有水平。一開始有個英文的自我介紹,由於有了一面時自我介紹的慘痛教訓,因此這裏稍微準備了一下,說得比較流利,結果面試官立刻就開始英文問問題,聊聊爲何喜歡微軟啊,還有項目啥的。沒多久我就表示不行了,能不能說中文,他說OK,而後就切換到中文。

而後開始作題,和前兩次在線編輯不同的是,這一次作題讓我打開本地的編輯器,他經過遠程桌面看我作題。他問我平時用什麼語言,我說C++和Python,他就讓我用C++,因而我就打開了VSCode。

第一個題目是,寫一個函數,驗證輸入的字符串是不是IPv4。IPv4的要求是有4段,每一段的數字在區間[0,255]內。這個題是蠻簡單的,可是他要求我不能用任何STL的函數,string都不行,連字符串轉int的方式也要本身寫。可是寫完以後還讓我舉出須要用到的測試用例,在不斷舉例的過程當中,發現本身代碼裏存在着好多bug,最終改了很久,終於改完了。

bool judgeIPV4(char* s) {
    if (!s) return false;
    int digit = -1;
    count = 0;
    while (*s != '\0') {
        if (*s >= '0' && *s <= '9' && digit == -1) {
            digit = 0;
        }
        if (*s >= '0' && *s <= '9' && digit != -1) {
            int temp = *s - '0';
            digit = digit * 10 + temp;
            if (digit > 255) {
                return false;
            }
        } 
        else if(digit == '.') {
            if (digit < 0) {
                return false;
            }
            count += 1;
            if (count > 3) return false;
            digit = -1;
        } else {
            return false;
        }
        s++;
    }
    if (count != 3 || digit < 0) return false;
    return true;
}

 

這個題從算法上來講比較簡單,可是須要考慮的狀況不少,好屢次我說我以爲沒問題了,他仍是讓我再看看,果真仍是有bug。。。最後一個bug是超過int範圍了的問題。雖然磕磕碰碰了好久,可是最終仍是完成了,又開始下一題。

我剛聽到下一題的時候我心裏是崩潰的,不是說好的三面只面一個題嗎???結果還好,第二題是我見過的,真的是運氣好。題目是給定前序遍歷和中序遍歷,重建二叉樹。這個題的核心是知道前序遍歷的第一個節點是root,中序遍歷中在root以前的節點屬於左子樹,root以後的屬於右子樹。

#include<iostream>
#include<vector>
using namespace std;
struct TreeNode
{
    int val;
    TreeNode *left;
    TreeNode *right;
    TreeNode(int x): val(x), left(NULL), right(NULL) {}
};

TreeNode *rebuiltTree(vector<int> pre, vector<int> mid) {
    if (pre.size() == 0) return NULL;
    TreeNode *root = new TreeNode(pre[0]);
    vector<int> left_pre, right_pre, left_mid, right_mid;
    bool flag = false;
    for (int i = 0; i < mid.size(); i++) {
        if(pre[0] == mid[i]) {
            flag = true;
            continue;
        }
        if(!flag) {
            left_pre.push_back(pre[i + 1]);
            left_mid.push_back(mid[i]);
        } else {
            right_pre.push_back(pre[i]);
            right_mid.push_back(mid[i]);
        }
    }
    root->left = rebuiltTree(left_pre, left_mid);
    root->right = rebuiltTree(right_pre, right_mid);
    return root;
}

 

寫完以後,他讓我想一想這個代碼裏存在的bug。我舉了三種,首先是若是節點值超過了int的範圍,第二是遞歸中佔用的內存超過 了堆的限制,第三是若是有重複的元素。。。他問我有重複的元素咋辦,我說那就沒有辦法重建了啊,他笑了笑說就這樣吧,這也是一個待驗證的問題。

 

面試結束後不久就收到了hr電話,經過了

相關文章
相關標籤/搜索