微软前端社招笔试详解

19,426 阅读5分钟

又到了每年的金三银四招聘旺季,有幸获得了微软的笔试机会,程序猿们应该都知道,老美的公司都喜欢怼数据结构与算法,微软肯定也不例外,个人觉得考数据结构对每一个应聘者都公平,我这次投的是微软苏研院,笔试考察的不难,是最最常见的数据结构算法裸题,这里记录一下,也给出解法。

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;
};

老美的公司,算法关必须过啊。所以,路漫漫其修远兮,刷题刷题多刷题吧