2021大厂秋招笔试题一览


September 21, 2020

上次聊理想,这次谈现实。本篇将分享笔者顶着12小时时差,清晨笔试凌晨面试,迷迷瞪瞪地获取到的本季校招第一手资料,供大佬们来年开春或是秋招备用。所有题目均只作大致描述,代码及详解会于后续文章发布,所有内容均隐去雇主及题目细节,仅供个人学习使用。

总体情况

个人参加了大概十场笔试。考试形式上,“选择题与编程题搭配服用”和“只考编程题,可能要求做性格测试”的雇主各占一半。出自 LeetCode 的原题很少,但基本上是换汤不换药,都是“瞎编一个故事 XD,然后让你套到某个具体数据结构和算法上去解题”,范浩强大神“编程竞赛水浅”的经验之谈诚不欺我(果然最难还是数学->发现新算法+证明其正确性Orz)。考试时长在两小时左右(本中年猹动作迟缓,经常笔试结束后才做完…)。

具体来看:

题目类型

BFS/DFS/MST/2-SAT

基于图的问题比较多,以下是常见考法:

实现上不需要用剪枝, A* 等特殊处理,或做线性 find-k 算法以及 Yao’s MST 构造算法(based on boruvka’s algorithm)等小众优化。

几小时前刚刚见到一个笔试题,出题者用十分刻意且奇怪的故事引导考生去解 2-SAT 问题并在此基础上计算可满足最多子句(Clause)的组合数,怪味十足hhhh。

Greedy

贪心(Greedy)策略导向的题目也比较多(比如上面提到的找最短路和最小生成树构造),具体例子还有给定一组区间,寻找最大不相交区间集的元素个数。该类问题最麻烦的是其常和动态规划仅一步之遥,开始误判了解题路线的话在有限时间里就很难拉回来了 :( 比如“计算最少插入数使括号匹配”的问题可以很方便地通过贪心解决,但涉及到括号的增/删/改甚至多种括号混用的情况就让人一头雾水,中途再去算编辑距离基本来不及了。

Math

考数学题的雇主很少,基本上只有微信娘家才会例行放一两道该类型题目,内容要么是求方程近似解,要么是计算某种具体情境下的概率, 不拼手速,没思路的话可以直接跳过(比如不知道 e 是 (1+1/n)^n 或以 n 的阶乘为底的级数的极限的话就机智弃题 :0 )

DP

动态规划类的题目不方便调库求解且形式多样,各家都很爱出此类题目,从最大子串和、最长上升子序列,到各种奇奇怪怪的背包。实在是门类繁多,结合树来考更可以玩出新花样。本质上讲,DP 实际还是递归的一种鸡贼实现方法,通过缓存子问题结果把原问题从 NP 拉到 P,本质上还是要先找到递归关系和递归基。(如果面试官出的题目全是判定 n 个元素的集合是否可以筛出部分元素求和为特定值的问题,大家都做个开心的 NPC 多好 :)

面试技巧

木有hhhh,只要考试不作弊,面试真心聊,适当换位思考,其他随缘就好,毕竟 offer 不能叠起来用,“以好充次”留给同辈更多机会岂不妙哉 XD 遇到心仪雇主,适当拽点”快排在细化的渐进复杂度意义下稍优于归并排序”的料也能使面试官眼前一亮,至于“料在何方”, 自然要祭出邓公的数据结构与算法 MOOC 大讲堂👍 数据结构(上), 数据结构(下), THUOJ, DSA.WORKSPACE,十二道编程题做了不吃亏,做了不上当(就是不能用 STL 各种结构全要手动实现 hhhh)

学术界与工业界的紧密联系仅是近年的事。尝试跳出校园,从追求 SOTA 到追求简单、高效、可扩展的实现(例: 实现并行版本的稀疏矩阵乘法)并培养业务思维才是一个决定“外出务工”的新毕业生更应专注的方向。

打完收工 :) 下期开始做详解和配代码,本期作测试用途先展示一个用 buff 加速 mergesort 并降低内存负载的小技巧,烦请大屏查看:

int merge_and_count(long long *arr, int lo, int mi, int hi, long long *buff)
{
    int res = 0;
    for (int i = lo; i < mi; i++) buff[i] = arr[i];
    
    int i = lo, j = mi, curr = lo;
    
    while (i < mi && j < hi) {
        if (buff[i] <= arr[j])
        {
            arr[curr] = buff[i];
            i++;
        } else {
            arr[curr] = arr[j];
            res += (mi - i);
            j++;
        }
        curr++;
    }
    
    while (i < mi) {
        arr[curr++] = buff[i++];
    }
    
    while (j < hi) {
        res += (mi - i);
        j++;
    }

    return res;
}


int count_by_mergesort(long long *arr, int lo, int hi, long long *buff)
{
    if (hi - lo < 2) return 0;
    int mi = (lo + hi) >> 1;
    int res1 = count_by_mergesort(arr, lo, mi, buff);
    int res2 = count_by_mergesort(arr, mi, hi, buff);
    int res3 = merge_and_count(arr, lo, mi, hi, buff);
    return res1+res2+res3;

}

ps, 麻烦 “share” 本篇给需要的同伴,后续内容会在此公众号继续刊发,笔芯♥️