微軟前端社招筆試詳解

白落梅發表於2019-03-14

又到了每年的金三銀四招聘旺季,有幸獲得了微軟的筆試機會,程式猿們應該都知道,老美的公司都喜歡懟資料結構與演算法,微軟肯定也不例外,個人覺得考資料結構對每一個應聘者都公平,我這次投的是微軟蘇研院,筆試考察的不難,是最最常見的資料結構演算法裸題,這裡記錄一下,也給出解法。

1 快速排序

快排應該是最網紅的題了,從校招到社招,從後端到前端再到移動端,都會問,但是估計能手撕出來的不超過一半,能講清楚思路的估計不超過一半的一半,其實用兩個詞概括,就是雙指標+遞迴分治,在每一輪遞迴的時候找到每個基準數排完序後的位置,將小於這個基準數的所有數放到它的左邊,將大於這個基準數的所有數放到它的右邊,然後再分別遞迴它左邊的陣列與它右邊的陣列。比如說陣列[2,3,1,5,6,4]:

微軟前端社招筆試詳解
初始狀態是這樣,我現在給兩個指標出來,一個指標指向頭,即arr[0] = 2, 一個指標指向尾,及arr[5] = 4,規定一個基準數,這裡直接用第一個數(arr[0] = 2)即可。開始第一次的遞迴處理,尾指標先從右往左掃,掃到第一個小於(注意是小於,而不是小於等於哦)基準數的位置停住,這時候頭指標再從左往右掃,掃到第一個大於基準數的位置停住,這時候是下面的圖示狀態:

微軟前端社招筆試詳解
交換兩個指標所指的數,成為了下面的狀態:

微軟前端社招筆試詳解
兩個數交換完畢,右指標此時指的是arr[2] = 3, 左指標指著arr[1] = 1;交換完畢後右指標繼續從當前位置往左掃,掃到1的時候發現和左指標相遇了,那麼這個時候就結束左右指標的掃描,左右指標同時指著arr[1] = 1,即:

微軟前端社招筆試詳解
此時退出迴圈掃描的過程,交換基準數與左右指標同時所指的數的位置,開頭說了,基準數我選擇的是arr[0] = 2, 指標指的是arr[1] = 1; 交換過後就變成了:

微軟前端社招筆試詳解
這時候就發現基準數已經出現在了它排完序後應該在的位置(排完序後是[1,2,3,4,5,6],2出現在了第2位),比這個基準數小的陣列出現在了它的左邊([1]出現在了2的左邊),比基準數大的出現在了它的右邊([3,5,6,4]出現在了2的右邊)。之後的過程就是對左右陣列的分別遞迴處理。附上程式碼:

    function quickSort(arr, begin, end) {
           //遞迴出口
           if(begin >= end)
               return;
           var l = begin; // 左指標
           var r = end; //右指標
           var temp = arr[begin]; //基準數,這裡取陣列第一個數
           //左右指標相遇的時候退出掃描迴圈
           while(l < r) {
               //右指標從右向左掃描,碰到第一個小於基準數的時候停住
               while(l < r && arr[r] >= temp)
                 r --;
               //左指標從左向右掃描,碰到第一個大於基準數的時候停住
               while(l < r && arr[l] <= temp)
                 l ++;
               //交換左右指標所停位置的數
               [arr[l], arr[r]] = [arr[r], arr[l]];
           }
           //最後交換基準數與指標相遇位置的數
           [arr[begin], arr[l]] = [arr[l], arr[begin]];
           //遞迴處理左右陣列
           quickSort(arr, begin, l - 1);
           quickSort(arr, l + 1, end);
       }

       var arr = [2,3,4,1,5,6]
       quickSort(arr, 0, 5);
       console.log(arr)
複製程式碼

思考:為什麼是右指標先掃而不是左指標先掃呢,大家自己想想吧哈哈,模擬一下就知道了。

發散性思考

上文中我提到了左陣列,右陣列,還提到了基準數的概念,其中,左陣列中的所有值都小於基準數,右陣列中的所有值都大於基準數,而且快排還是個遞迴的過程。我這裡如果做一下類比,如果我把基準數看成是樹根,把左陣列看成是根的左孩子,右陣列看成是根的右孩子,相信學過資料結構的童鞋都知道了,那不就是一顆BST嗎。哈哈確實如此,其實你會發現BST的中序遍歷出來就是一個有序陣列,所以上文中快排的過程就是一個建立二叉搜尋樹的過程。

二叉查詢樹(Binary Search Tree),(又:二叉搜尋樹,二叉排序樹)它或者是一棵空樹,或者是具有下列性質的二叉樹: 若它的左子樹不空,則左子樹上所有結點的值均小於它的根結點的值; 若它的右子樹不空,則右子樹上所有結點的值均大於它的根結點的值; 它的左、右子樹也分別為二叉排序樹.

所以要是問你怎麼建立一顆BST,想必也能信手拈來了吧,廢話不說上程式碼:

     function vNode(value) {
           this.val = value;
           this.left = this.right = null;
       }
     function createBST(arr) {
           var len = arr.length;
           if(len < 1)
               return;
           var l = 0;
           var r = len - 1;
           var temp = arr[0];
           while(l < r) {
               while(l < r && arr[r] >= temp)
                   r --;
               while(l < r && arr[l] <= temp)
                   l ++;
               [arr[l], arr[r]] = [arr[r], arr[l]];
           }
           [arr[0], arr[l]] = [arr[l], arr[0]];
           var root = new vNode(arr[l]);
           root.left = createBST(arr.slice(0, l));
           root.right = createBST(arr.slice(l + 1));
           return root;
       }
複製程式碼

二叉樹的非遞迴中序遍歷

我相信很多人都能寫出二叉樹的遞迴遍歷:遞迴遍歷左子樹,輸出根節點,遞迴遍歷右子樹,三行程式碼,完事,但是要問到非遞迴遍歷,估計大多數人就傻眼了,但是人家就要考你不會的啊,非遞迴遍歷其實就是藉助棧來完成:

var inorderTraversal = function(root) {
    const res = [];
    const stack = [];
    while(root||stack.length !== 0)
     {
          while(root)
          {
              stack.push(root);
             root=root.left;
         }
         if(stack.length)
         {
             let p=stack[stack.length - 1];
             res.push(p.val);
             stack.pop();
             root = p.right;
         }
     }  
    return res;
};
複製程式碼

老美的公司,演算法關必須過啊。所以,路漫漫其修遠兮,刷題刷題多刷題吧

相關文章