1. 京东2019春招算法类试卷,到底在筛选什么样的人
每年春招的算法类试卷一出来,总能引起一波讨论。2019年京东这场春招算法岗笔试,我印象比较深——它不是单纯堆难题,而是把计算机基础、机器学习理论和业务直觉揉在一张卷子里。很多同学刷了一堆LeetCode去考试,结果挂在了一些看起来"基本功"的题上,挺可惜的。
先说说京东算法岗的JD。我当时帮团队筛过简历,也参与过笔试出题方向的讨论。京东的算法岗位大致分两类:一类偏推荐搜索、供应链优化、风控建模这类业务算法,另一类偏机器学习平台、基础算法研究。虽然方向不同,但笔试考的东西高度重叠——数据结构与算法、机器学习基础、概率统计、编程能力。试卷的目的不是选出"刷题最多的人",而是筛出"基础扎实、能写代码、懂模型原理、有业务sense"的候选人。
我当时拿到这套2019春招题的时候,第一反应是:题目整体难度中等偏上,但没有特别偏难怪的题。它的核心逻辑是——你不需要会所有冷门算法,但常见的数据结构、排序、字符串匹配、动态规划得能写对;机器学习部分,逻辑回归、决策树、SVM这些经典模型的基本原理必须讲清楚;再留一两道开放题,看看你拿到一个真实业务问题时的思考路径。
整套卷子大概分三块:选择题(覆盖数据结构、操作系统、网络、概率统计)、手写代码题(重点考察KMP、排序、二分、DP这类高频考点)、算法策略题(涉及PID、卡尔曼滤波、积分类算法的应用场景理解)。接下来我把每部分拆开讲,结合具体的考点和解题思路,帮大家还原一下这张卷子的考察逻辑。
2. 数据结构与基础算法:高频考点逐题拆解
2.1 KMP算法与next数组——字符串匹配的经典陷阱
热搜词里挂着"在kmp算法中,对于模式串p='abacaba',其next数组(next[i]定义为...)",这道题几乎可以确定是京东笔试的原题,或者至少是同源变体。字符串匹配是算法岗笔试的常客,而KMP算法又是字符串匹配里考得最频繁的一个。
先说下KMP到底解决什么问题。普通暴力匹配,两个指针分别在文本串和模式串上走,失配的时候文本串指针回退到下一个位置,模式串指针回退到开头,时间复杂度O(n*m)。KMP的核心思想是:失配的时候,模式串指针不要回退到开头,而是回退到一个"已经匹配过的前缀"的位置,文本串指针不回退。这个"回退到哪"的信息,就存在next数组里。
next数组的定义在这道题里写得很清楚:next[i]表示模式串前i个字符组成的子串中,最长相同前后缀的长度。注意这里有一个常见的坑——不同教材对next数组的下标定义不太一样。有的版本next[0]=-1,有的版本next[0]=0。京东这道题明确说了"next[i]定义为",说明它采用的是某种固定定义,做题前一定要先搞清楚下标从几开始。
拿"abacaba"这个模式串来算一遍。先写出它的前缀子串:
- 前1个字符"a":没有真前缀和真后缀,最长相同前后缀长度为0
- 前2个字符"ab":前缀"a"、后缀"b",不相等,长度为0
- 前3个字符"aba":前缀"a"、"ab",后缀"a"、"ba",最长相同前后缀是"a",长度为1
- 前4个字符"abac":前缀"a"、"ab"、"aba",后缀"c"、"ac"、"bac",没有相等,长度为0
- 前5个字符"abaca":最长相同前后缀是"a",长度为1
- 前6个字符"abacab":最长相同前后缀是"ab",长度为2
- 前7个字符"abacaba":最长相同前后缀是"aba",长度为3
所以next数组(从1开始计数)就是[0, 0, 1, 0, 1, 2, 3]。如果next[0]定义成-1,那整体往后平移加调整,结果会呈现另一种形态。考场上最稳妥的做法是:先用题目给出的定义,把next数组的语义理清楚,再在草稿纸上把每个位置的值写出来。我见过太多人背了模板,碰到"next[i]定义为"这种字眼直接默认成自己熟悉的那套,结果一开始就算错。
KMP的失配跳转是:当模式串第j位失配时,j回退到next[j](按某种定义可能是next[j-1]),继续比较。整个算法的时间复杂度是O(n+m),因为文本串指针最多移动n次,模式串指针虽然有回退,但回退的总次数不会超过前进的总次数。这个复杂度证明在面试里偶尔会被追问,建议自己推一遍。
2.2 排序算法全家桶:不只会写快排,还得知道什么时候用堆排序
热搜词里"数据结构排序算法"、"冒泡排序算法c++"、"堆排序算法"扎堆出现,说明排序这块确实是笔试选择题和编程题的重灾区。京东的卷子里排序题一般不会让你手写一个完整的排序,而是考你"在不同场景下选哪种排序"和"某种排序的复杂度推导"。
先给一个我自己整理的排序算法对比表,笔试前建议背熟:
| 排序算法 | 平均时间复杂度 | 最坏时间复杂度 | 空间复杂度 | 稳定性 |
|---|---|---|---|---|
| 冒泡排序 | O(n²) | O(n²) | O(1) | 稳定 |
| 选择排序 | O(n²) | O(n²) | O(1) | 不稳定 |
| 插入排序 | O(n²) | O(n²) | O(1) | 稳定 |
| 希尔排序 | O(n^1.3左右) | O(n²) | O(1) | 不稳定 |
| 归并排序 | O(nlogn) | O(nlogn) | O(n) | 稳定 |
| 快速排序 | O(nlogn) | O(n²) | O(logn) | 不稳定 |
| 堆排序 | O(nlogn) | O(nlogn) | O(1) | 不稳定 |
京东这种大厂笔试,特别喜欢考堆排序。原因是堆排序在很多工程场景里是"优先队列"的底层实现,比如TopK问题、任务调度、定时器。我记得有一道选择题大概是"从一个无序数组中找出最大的K个数,最优解法的时间复杂度是什么"。如果你只会排序后取前K个,那是O(nlogn);用堆来做,维护一个大小为K的最小堆,遍历一遍数组,每次和堆顶比较,时间复杂度是O(nlogK)。K远小于n的时候,这个差距非常明显。
再比如"冒泡排序c++"这个热搜词,说明还是有不少人把最基本的冒泡当作复习起点。冒泡排序的优化点在于:如果某一趟没有发生任何交换,说明序列已经有序,可以提前终止。这个优化在笔试里经常被拿出来当作选择题,问你"最好的情况下冒泡排序的时间复杂度",答案是O(n)而不是O(n²)。
考场上我还遇到过一个排序的衍生题:链表排序。数组的快排可以直接通过下标访问元素,但链表不行。链表排序一般用归并排序,因为归并排序只需要顺序访问,不需要随机访问。LeetCode 148就是原题。链表归并排序的代码量不算大,但边界条件容易写错,建议考前亲手写两遍。
2.3 二分、贪心、动态规划:笔试编程题的三大主力
编程题部分,京东倾向于考三类:二分查找及其变体、贪心策略题、经典动态规划。这三个方向在热搜词里都有对应,比如"dijkstra算法"、"快速幂算法c++"、"剪枝算法"、"贪心算法"。
先说二分。二分查找本身很简单,但笔试爱考的是变体:查找第一个等于目标值的位置、最后一个等于目标值的位置、插入位置。这些变体本质上都是"二分边界"问题。我推荐一个万能的写法:定义区间[l, r]为左闭右闭,循环条件为l <= r,更新时l = mid + 1或r = mid - 1。这样写可以避免死循环,而且能方便地处理边界情况。京东笔试里有一道经典题:"旋转数组中的最小数字",就是二分的变体——数组是由一个有序数组旋转得到的,你需要在O(logn)时间内找到最小值。核心思路是:比较mid和r的值,如果nums[mid] > nums[r],说明最小值在右半部分;否则在左半部分。
贪心算法一般是给一个场景题,比如"活动安排问题"、"跳跃游戏"。做贪心题最关键的是证明贪心策略的正确性。笔试不需要你写严格的数学证明,但你要能说服自己。以跳跃游戏为例,维护一个当前能到达的最远位置,遍历数组,不断更新这个最远位置,如果某个位置i大于当前能到达的最远位置,说明无法到达,返回false。这个贪心的正确性在于:每一步都扩展当前最远可达范围,任何其他策略都不会比这个更优。
动态规划是重头戏。京东2019年春招我记得有一道"最大连续子数组和"的变体。经典解法是Kadane算法:用dp[i]表示以第i个元素结尾的最大子数组和,转移方程是dp[i] = max(nums[i], dp[i-1] + nums[i]),最终答案是所有dp[i]中的最大值。空间上可以优化成只用一个变量。这个题太经典了,以至于出现在笔试里反而让人容易大意——有些人直接背了模板,结果题目改成"环形数组"或者"允许删除一个元素"就懵了。我的建议是:DP题一定要先明确状态定义,再写转移方程,最后再看边界条件。状态定义对了,代码就是照着翻译。
3. 机器学习与深度学习:算法工程师的核心战场
3.1 从"特征工程"到"模型评估":逻辑回归为什么是必考题
京东算法岗笔试的选择题里,机器学习的比例很高。逻辑回归是出现频率最高的一个,因为它是理解分类模型的基础,也是推荐系统、风控模型的常见起点。考法一般有三种:推导损失函数、解释正则化的作用、问它和线性回归的区别。
逻辑回归的损失函数是交叉熵损失,这点大多数人能答上来,但"为什么不用均方误差(MSE)"这个问题能难住不少人。原因是:逻辑回归的预测值是经过sigmoid变换的,输出在(0,1)区间。如果用MSE,损失函数关于参数不是凸函数,梯度下降容易陷入局部最优;而交叉熵损失关于参数是凸函数,能保证收敛到全局最优。此外,sigmoid函数在两端梯度饱和,如果用MSE,梯度会非常小,收敛极慢;交叉熵损失在预测值和真实值差异大时梯度也大,学习效率更高。
正则化也是高频考点。L1正则化会把参数压缩到0,产生稀疏解,适合做特征选择;L2正则化只会让参数趋近于0但不会等于0,能防止过拟合但不会产生稀疏性。为什么L1有稀疏性?因为L1的梯度是常数,在参数接近0的时候,优化过程很容易把参数直接推过0;而L2的梯度大小和参数本身成正比,参数越小梯度越小,最后只会无限接近0。
关于模型评估,京东考过AUC和ROC。AUC的含义要能说清楚:随机抽取一个正样本和一个负样本,分类器对正样本的打分高于负样本的概率。所以AUC对样本类别不平衡不敏感,适合正负样本比例悬殊的场景。相比之下,准确率(Accuracy)在正样本占99%的数据集上没有任何意义——全都预测为正样本,准确率也有99%。这种基础概念在笔试里就是送分题,但如果你只背了"AUC越大越好"而不知道它到底在衡量什么,稍微换个问法就露馅了。
3.2 反向传播与激活函数:深度学习笔试的高频送分题
深度学习部分,京东2019的卷子主要考了反向传播的推导和激活函数的比较。这两块是所有深度学习岗的必考题,也是区分"调包侠"和"真正懂原理的人"的分水岭。
反向传播的推导,核心是链式法则。给定一个简单的网络:输入x,隐藏层h = W1x + b1,经过激活函数a = sigmoid(h),输出y = W2a + b2,损失函数L = (y_target - y)²。反向传播就是从损失函数出发,先算dL/dy,然后算dL/dW2和dL/db2,再算dL/da,接着通过激活函数的导数dL/dh,最后算dL/dW1和dL/db1。笔试里一般不会让你算特别深的网络,但至少两层全连接网络的推导必须能完整写出来。
激活函数的比较也是一个经典考点。sigmoid的缺点:输出不是零中心的,会导致后一层神经元接收到的输入始终为正或始终为负,使得梯度更新呈锯齿状;而且两端饱和,梯度消失严重。tanh的输出是零中心的,但两端还是会饱和。ReLU解决了正区间的饱和问题,计算也简单,但有一个"死神经元"问题——如果某个神经元的输入始终为负,它的梯度始终为0,权重永远不会更新。Leaky ReLU、PReLU都是针对这个问题的改进。京东有一道选择题就问"ReLU相比sigmoid的优势不包括以下哪项",选项里有"减轻梯度消失"、"计算简单"、"输出零中心"、"加速收敛"。答案是"输出零中心",因为ReLU的输出都是非负的,它并不具备零中心的性质。
3.3 集成学习:随机森林和GBDT的对比记忆
集成学习在搜索和推荐场景用得非常多,京东的算法岗笔试自然也会涉及。考得最多的是Bagging和Boosting的区别,以及随机森林和GBDT的具体差异。
我建议用一个表来对比记忆:
| 维度 | 随机森林 | GBDT |
|---|---|---|
| 集成方式 | Bagging | Boosting |
| 基学习器 | 决策树(通常CART) | 决策树(CART回归树) |
| 样本采样 | 有放回抽样(Bootstrap) | 每轮用全部样本,但调整权重 |
| 特征采样 | 每次分裂随机选部分特征 | 全部特征参与分裂 |
| 树的关系 | 并行独立训练 | 串行,每棵树拟合残差 |
| 目标函数 | 最小化每棵树的分类误差 | 最小化整体损失函数 |
| 抗过拟合 | 较强 | 较弱,需要用shrinkage控制 |
| 训练速度 | 快(可并行) | 慢(串行) |
GBDT的核心是"加法模型+前向分步算法"。每一步训练一棵新的决策树,拟合的是损失函数对当前模型预测值的负梯度。对于平方损失,负梯度就是残差;对于其他损失函数,负梯度是残差的近似。XGBoost在GBDT基础上做了二阶泰勒展开,加入了正则项,还支持列采样,这些都是笔试进阶题会考的。
京东2019的卷子里有一道题我印象很深:"随机森林中每棵树的训练数据是如何获取的",选项里有"独立同分布采样"、"有放回采样"、"无放回采样"、"全部数据"。答案是"有放回采样",也就是Bootstrap。很多人选了"独立同分布采样",这不对——Bootstrap虽然是独立采样,但样本不是同分布的,因为每次采样后会把样本放回,同一个样本可能被多次抽到。这个细节很基础,但恰恰是区分"真懂"和"背概念"的点。
4. 算法策略题与开放题:把控制论知识拉出来遛遛
4.1 PID算法与卡尔曼滤波:为什么算法试卷会出现控制论内容
热搜词里"pid算法"、"pid算法在crps psu power的作用"、"卡尔曼滤波算法"、"增量式pid算法"刷了一波存在感。很多人会觉得奇怪:京东一个互联网公司,考PID干嘛?这里要理解京东的业务场景——京东有自营物流、仓储机器人、无人机配送,还有大量的供应链预测和库存控制问题。PID算法虽然起源于自动控制,但它的思想在库存控制、价格调整、流量分配这些场景里都有应用。
试卷里PID的题一般不会让你做复杂的控制理论推导,而是考你对"比例、积分、微分"三个词的理解。比例项(P)是对当前误差的反应,误差越大,调整力度越大。积分项(I)是对历史误差的累积,作用是消除稳态误差——如果系统一直存在一个小的偏差,比例项可能不够力,积分项会不断累积,把这个偏差拉回来。微分项(D)是对误差变化趋势的预测,误差在快速增大时,微分项会提前加大抑制力度,减少超调。
增量式PID和位置式PID的区别也是考点。位置式PID输出的是控制量的绝对值,一旦计算错误影响很大;增量式PID输出的是控制量的增量,只和最近几次的误差有关,计算量小,而且不会累积误差。在嵌入式系统和机器人控制里,增量式更常用,因为它不需要对所有历史误差积分。
卡尔曼滤波则出现在一道关于"无人机定位数据融合"的题里。卡尔曼滤波的核心思想是:把传感器的测量值和系统模型的预测值融合起来,根据两者各自的不确定性(协方差)加权,得到最优估计。笔试里不需要你推导卡尔曼增益的公式,但你要能说明白"预测-更新"两个步骤,以及为什么它能融合多个噪声传感器。
我当时复习的时候也觉得很意外,后来想通了:大厂算法岗考的从来不只是算法,而是"解决实际工程问题的能力"。控制论算法出现互联网公司的笔试卷里,反映的是业务趋势。备考时别只盯着机器学习,花点时间了解PID、卡尔曼滤波这些经典算法的思想,性价比很高。
4.2 概率题和智力题:考察的是数学建模能力
算法岗笔试里概率题几乎是必考的。京东2019春招有一道题大概是:"一个盒子里有红球和白球若干,每次随机取一个球,记录颜色后放回,问取到第一个红球时取球次数的期望。"这是一个典型的几何分布问题:单次取到红球的概率是p,期望就是1/p。
这类题的难点不是公式,而是把实际场景转化为概率模型。我见过不少同学能把几何分布、二项分布的公式背得滚瓜烂熟,但题目稍微换个包装就认不出来。比如"在一条长度为L的线段上随机扔两个点,求两点距离的期望"——这其实是几何概率和顺序统计量的问题。再比如"有N个房间,每个房间有一个人,M个人随机进入房间,求空房间的期望数量"——这考察的是指示变量和线性期望的性质。
做概率题有一个经验法则:如果题目问的是期望,优先考虑用指示变量把期望拆成多个事件概率之和,而不是直接算联合分布。以空房间问题为例,令Xi表示第i个房间为空的事件,E[Xi] = P(第i个房间为空) = ((N-1)/N)^M。根据期望的线性性质,空房间数量的期望就是N * ((N-1)/N)^M。这个解法干净利落,而且不需要考虑房间之间是否独立——期望的线性性对任意随机变量都成立,哪怕它们不独立。
智力题部分,京东考过"25匹马,5个赛道,最少比赛多少次找出最快的3匹马"和"100层楼扔鸡蛋"这类经典题。25匹马问题答案是7次:先分5组比5次,每组第一名再比一次确定总冠军,然后根据第6次的结果,排除所有不可能进前三的马,最后再比一次。扔鸡蛋问题的答案是14次(最优解法是逆向思维:第一次从14楼扔,然后逐次减1)。这些题本质上都是"信息论"和"最坏情况最优"的考察,平时多刷刷牛客和LeetCode的智力题模块,考试时遇到就能秒杀。
5. 备考路线图:三个月从刷题到笔试实战
5.1 算法岗笔试的高频考点优先级排序
结合京东2019春招的试卷和后续几年的大厂笔试题,我整理了一个考点优先级列表,备考时可以按这个顺序分配精力:
第一梯队(必须熟练):数组/链表、栈/队列、哈希表、二叉树遍历(前中后序、层序)、二分查找及变体、快速排序/归并排序/堆排序、动态规划(背包、子序列、子数组)、KMP/字符串匹配、贪心算法、DFS/BFS、最小生成树/最短路径(Prim、Kruskal、Dijkstra)。
第二梯队(应该掌握):并查集、线段树/树状数组、Trie树、拓扑排序、LRU缓存、位运算技巧、滑动窗口、双指针、单调栈/单调队列、快速幂、回溯(全排列/组合/子集)、剪枝优化。
第三梯队(有精力再看):后缀数组、DC3、网络流、Treap/Splay、蓄水池抽样、BM算法、AC自动机。
京东2019的卷子主要落在第一梯队。KMP、排序、二分、DP、贪心这几块占了编程题的大头。第二梯队里并查集和滑动窗口在后续年份的笔试里出现频率变高了,建议不要跳过。
5.2 模拟笔试的三个关键习惯
考前两周一定要进行模拟笔试。做题环境和平时刷LeetCode差别很大:时间有限、不能暂停、不能看题解、每道题都要写出完整可运行的代码。我自己总结的三个关键习惯,分享给大家:
第一,先读题再写代码,至少花5分钟理解题意。很多笔试挂掉不是因为不会做,而是题目看了个大概就动手,最后漏掉了边界条件。比如题目里说"数组长度可能为0",那你的代码开头就必须处理n=0的情况。面试官看代码的时候,首先是看边界处理,其次才看算法复杂度。
第二,先写暴力解再优化。笔试不是面试,不会有人因为你写出了暴力解而扣分——前提是你最终给出了优化解。如果你一开始就想写O(nlogn)的最优解,又没想清楚,很可能卡在中间写不下去。我通常的做法是:先快速写一个暴力解保底,再在此基础上优化。这样即使优化失败,至少有一份能跑的代码。
第三,用草稿纸算例推演。笔试系统一般提供编译运行,但不能实时调试。KMP的next数组、DP的转移方程,这些在代码里出bug很难通过肉眼发现。我的习惯是:写完代码后,自己构造几个测试用例,在草稿纸上手动跑一遍,确认和代码的逻辑一致。特别是KMP这种有回退指针的算法,手动推演一遍能发现大部分边界问题。
5.3 资料推荐与刷题路径
我当年备考用的资料可以给大家一套组合拳:
- LeetCode:按标签刷,优先刷数组、链表、树、DP、字符串、二分查找这六个标签,每题先自己想20分钟,想不出来再看题解。重点不是过题量,而是每道题都知道为什么这么做。
- 剑指Offer:这个经典到不用多介绍了。京东笔试的风格和剑指Offer的很多题目重合度很高,特别是链表反转、二叉树重建、斐波那契数列这类基础题。
- 牛客网真题:有大厂历年笔试题库,考前一定要刷几套京东的真题,感受一下真实的题型分布和时间压力。
- 《算法导论》:不用全读,看红黑树、动态规划、贪心、图算法这几个章节的经典证明即可。笔试虽然不考证明,但理解了原理之后,写代码的底气完全不一样。
- 面经和博客:知乎、CSDN上有很多京东算法岗的面经,多看看别人回忆的真题,查漏补缺。
我当时还特意整理了"一页纸速查表",把KMP的next数组计算方法、Dijkstra的模板、DP的常见状态定义、排序算法的稳定性这些高频考点写在一页A4纸上,考前10分钟快速过一遍。这个习惯帮我避开了很多低级错误。
总结一下:京东2019春招算法类试卷的风格是"基础为主、应用为辅、业务渗透"。你不需要掌握所有冷门算法,但经典的数据结构、排序、字符串匹配、DP、机器学习基础必须滚瓜烂熟。备考时把精力放在高频考点上,多模拟笔试环境,练到手写代码不卡壳,这张卷子没有想象中那么难。祝大家顺利上岸。