
先说明一点这份京东2016研发工程师笔试题在当年的技术圈里流传度相当高。不少后来进大厂的朋友早期都拿它练过手。以现在的眼光回头看它虽然不像现在有些公司的笔试那么“卷”算法竞赛题但胜在基础、全面非常能反映一个研发工程师的基本功是否扎实。我最近又把这套题翻出来重新做了一遍结合当年刷题和后来面试别人的经验写一篇详细的拆解和复盘希望对正在准备笔试或者想夯实基础的朋友有帮助。1. 整体复盘这份笔试题到底在考什么这份试题给我的整体感觉是重基础、重原理、重代码功底但不过分追求偏题怪题。它覆盖了计算机基础学科的几大核心板块同时又加入了实际工程中才会用到的经验性判断。对于2016年的京东来说研发岗的招聘量很大需要快速筛选出基础扎实、能直接上手干活的人所以题目设计得相当务实。从题目结构来看大致可以分成几类考察方向典型考点占比估算难度感受数据结构与算法链表、树、排序、动态规划、字符串约35%中等部分题目有区分度操作系统与网络进程线程、内存管理、TCP/IP、HTTP约25%中等偏概念理解编程语言基础C/Java语法、内存模型、STL/集合类约20%偏易但坑不少数据库与LinuxSQL语法、索引原理、常用命令约10%中等考实操经验逻辑与智力题概率、推理、数学应用约10%偏难拉开差距的地方这套题的区分度其实很高。如果只是背概念选择题还能蒙一蒙一到编程题和逻辑题基本功扎不扎实立刻就能看出来。尤其是最后那道动规题和某道字符串处理的题我当时做的时候卡了挺久后来仔细琢磨发现它考察的其实不是你会不会背模板而是你能不能把问题抽象成已知的模型。1.1 为什么这套题到现在还有参考价值现在很多公司的笔试已经变成了LeetCode风格的纯算法题甚至有些直接就是Hard难度的原题反而对基础学科知识的覆盖少了。但实际工作中你会发现真正影响你代码质量的下限不是你会不会做LeetCode Hard而是你懂不懂操作系统原理、网络协议、数据库索引机制。这套题恰恰能帮你把这部分底子重新补起来。另外这套题的编程题设计得很典型属于“面试官手动改卷时会重点看思路”的类型。它不会让你写个红黑树或者手写堆排序那么极端但要求你能写出边界条件完整、复杂度达标的代码。这个度其实比LeetCode更接近真实面试。还有一点值得说的是这套题的选择题里埋了不少“坑”比如运算符优先级、Java的Integer缓存、C的拷贝构造时机、TCP的TIME_WAIT状态等。这些点不是死记硬背能答对的必须有实际写代码的经验才能避开。2. 经典题目选讲那些年我们一起踩过的坑这一章我挑几道代表性题目详细讲讲解题思路、易错点以及题目背后考察的知识点。每道题我都会给出“为什么这么考”的分析因为这比单纯对答案重要得多。2.1 选择题中的“送命题”运算符与类型转换这套题里有几道关于C/Java运算符和类型转换的题看起来简单但错误率极高。比如有一道是考察i和i在表达式中的求值顺序还有一道考察整数除法与浮点数转换的精度问题。先说整数除法这道题典型的坑是这样的int a 5 / 2;结果是2这个大多数人都知道。但改成double b 5 / 2;结果还是2.0而不是2.5。原因在于整数除法先执行结果已经是整数2然后再转成double。如果要得到2.5必须写成5.0 / 2或者(double)5 / 2。这道题的陷阱在于很多人以为“赋值给double类型的变量结果就会自动变成浮点数”但实际上类型转换发生在赋值之前运算时两个操作数都是int结果就只能是int。这种题考察的是对语言底层运算规则的理解而不是死记硬背。我在实际面试别人时也经常用这类题真的能筛掉一部分“只会写业务代码不懂语言机制”的候选人。再来说说i和i。在C中i返回的是旧值i返回的是新值这在自定义类型中尤其明显——i需要一个临时对象来保存旧值所以性能上比i略差虽然现代编译器通常会优化掉。Java中则规定表达式中i的求值时机比如int i 0; int j i i;最终结果是多少答案是2。原因是i先取旧值0但此时i已经变为1接着i先把i变为2再取新值2所以0 2 2。这类题目对不熟悉求值顺序的人来说基本靠猜。我的建议是实际写代码时永远不要在一个表达式里对同一个变量做多次自增或自减这种代码除了为了笔试没有任何工程价值。真要用的话分开两行写可读性还更好。2.2 数据结构题链表的边界处理这套题里有一道链表相关的编程题要求实现某种链表的操作比如反转、删除特定节点考察的核心其实是指针操作的边界处理。以反转单链表为例很多人能写出大致框架但很容易漏掉空链表、只有一个节点、链表长度为奇数/偶数这些边界情况。更关键的是如果要求用递归实现很多人会在递归返回条件上出错或者忘记了递归后需要断开原来的next指针。我当时做题时总结了一个口诀“先存后指再移动”——先用临时变量存住下一个节点地址再断开当前节点的next指向最后再把临时变量赋给当前节点。这个套路对所有链表指针操作题都适用。链表题最容易犯的错误就是“丢失指针”原因就是没有先存后指。二叉树相关的题目也考到了。比如层序遍历很多人知道用队列实现但容易忽略“如何区分每一层”。如果是按层输出需要在每一层开始时记录当前队列的长度一次性处理完这一层的节点再进入下一层。这个技巧在LeetCode上叫“BFS的分层处理”在实际面试中也是高频考点。2.3 算法核心题动态规划与字符串处理这套题最核心的编程题是一道动态规划我印象很深。它的背景大概是某种背包变体或者字符串匹配问题。这种题在2016年属于“区分度”极高的题——见过DP套路的人十分钟能写出来没见过的人想一小时也未必有思路。以背包问题为例标准的0-1背包状态转移方程是dp[i][j] max(dp[i-1][j], dp[i-1][j-w[i]] v[i])。考的时候不会直接告诉你这是背包问题而是会给你一个生活化的场景比如“京东仓库装货每件商品有重量和价值求在限重内能装走的最大价值”。这种包装方式恰恰就是工程问题的正常形态——你遇到的业务问题永远不会告诉你该用哪个算法需要你自己抽象。我当时做题时的思路是先确认是否具备最优子结构——前i件物品在容量j下的最优解是否可以通过前i-1件物品的最优解推导出来。答案是肯定的。再确认无后效性——当前选择只影响后续状态不受之前具体怎么选的影响。这是DP能用的两个前提条件。如果这两个条件不满足就要考虑换思路了。另一个重点是状态压缩。二维数组的空间复杂度是O(n*m)如果n和m都很大内存不够用。这时可以用滚动数组优化到O(m)因为每一行的计算只依赖上一行。如果你能看到这一层说明对DP的理解不是背模板而是真的吃透了。字符串处理那道题我记得有个典型的case是最长公共子序列LCS。LCS的DP方程是if s1[i]s2[j]: dp[i][j]dp[i-1][j-1]1; else: dp[i][j]max(dp[i-1][j], dp[i][j-1])。边界条件是第一行第一列置0。实现时要注意字符串是从下标0开始所以dp数组的大小要各加1否则会越界。这个“多加一行一列作为哨兵”的技巧在实际编码中能省掉大量if判断代码更简洁。2.4 操作系统与网络面试必问的基础题选择题里考了进程和线程的区别、死锁产生的必要条件、虚拟内存的作用、TCP三次握手和四次挥手、TIME_WAIT的意义等。这些题单看都不难但组合在一起就能看出一个候选人到底有没有系统学过理论。有一个比较有争议的点是“TCP握手为什么是三次而不是两次”。很多人背答案是“防止失效的连接请求到达服务端”但真要解释清楚需要画状态迁移图。如果是两次握手假设客户端发送的第一个请求在网络中滞留了很长时间客户端超时重发第二个请求这时服务端只收到第二个请求并建立了连接。那第一个滞留的请求后来也到达了服务端服务端会误以为是一个新连接于是再建立一个连接白白浪费资源。三次握手让服务端有确认能力可以避免这个问题。TIME_WAIT也是高频考点。主动关闭连接的一方在发送最后一个ACK后会进入TIME_WAIT状态持续2*MSL时间。原因一是确保最后一个ACK能到达对方如果丢失对方会重发FIN二是让网络中所有旧连接的报文自然消失避免影响新连接。这个知识点在开发高并发服务时很重要——你可能会遇到端口不够用的情况就是因为大量连接处于TIME_WAIT状态。2.5 数据库与Linux经验分水岭数据库部分考了索引、SQL编写和事务隔离级别。索引题目考的是B树相关原理——为什么用B树而不是二叉树为什么非叶子节点不存储数据等。当时很多不了解数据库底层原理的人会在这部分丢分但只要实际调优过SQL理解起来就很容易。比如那个经典问题联合索引(a, b, c)查询条件只有b时能不能走索引答案是不能。因为联合索引的最左前缀原则必须从a开始连续匹配。但如果是覆盖索引也就是查询的字段恰好都在索引里那么即使在最左前缀不满足的情况下也可能通过索引扫描来减少回表。这就有必要真正理解了。Linux命令考了grep、awk、sed、netstat、top、ps等常用命令的用法。题目本身不难但足以区分“见过Linux”和“用过Linux”。比如问查看端口8080被哪个进程占用该用什么命令。答案其实是netstat -tlnp | grep 8080或者新版推荐的ss -tlnp | grep 8080。这个命令组合在工作中太常用了没有实操经验的人回答不出来。2.6 逻辑与数学题拉开差距的秘密武器最后一部分逻辑题往往是整套卷子里失分最惨烈的。例如有些概率题看似能算其实陷阱藏在“是否独立事件”的假设里。还有些题表面考查排列组合但实际上用反面求解1减去对立事件的概率会更快。我记得有一道典型的题大概是“三个人轮流射击命中率不同问某人先射中的概率”。这种题有两种解法一种是直接用等比数列求和设第一轮某人命中的概率为p他在第一轮命中的概率是p如果第一轮所有人都没中情况回到起点概率乘上三人都没中的概率。于是可以列方程求解。这类题的共同特征是状态会在某个循环后回到自身用方程求解是最快的路径。另一类常考的是二分查找的变体。比如一个有序数组可能发生了旋转让你在O(log n)时间内找到最小值或某个目标值。思路是每次比较中间值和右边界值如果中间值小于右边界说明右半部分有序最小值在左边否则最小值在右边。这种题目把“基础算法”和“逻辑推理”结合得很好值得重点准备。3. 笔试现场的做题策略与时间分配做这份题的时候最怕的是“一道题卡太久导致后面会的也没时间写”。这套题虽然整体难度适中但我记得编程题如果按最优解写代码量并不小——尤其动态规划和字符串处理那两道题即使思路清晰手写代码加调试也需要至少20分钟一道。所以时间管理非常重要。我的建议是按下面的顺序来先花5分钟浏览全卷。把题目简单分类会做的、需要思考的、完全没思路的。用铅笔在题目边上做记号。优先做选择题中的简单题和中等题。选择题通常每题1-2分性价比很高但不要在一道题上超过3分钟。如果完全没有把握先标记下来回头再用排除法蒙一个。再做编程题里思路明确的题。每道编程题留足15-20分钟。如果一道题超过20分钟还没调通就先把当前思路的框架写上确保有步骤分——笔试改卷和ACM现场评测不同很多时候人工看思路和代码规范性。最后攻逻辑题和难题。这部分分值可能不高但属于“做出来一道就能拉很多分”的类型留到最后冲刺是合理的。我当年做题还有一个习惯编程题先在草稿纸上把关键变量和边界条件写一遍再在答题区写正式代码。这样能大幅减少因为思路混乱导致的反复涂改。注意手写代码时字迹和排版比你想象的更重要。改卷人一天要看几百份卷子如果代码排版清晰、变量命名规范、关键逻辑有注释观感上的优势是实打实的。4. 复盘与针对性准备从一套题看一类面试做完这套题我觉得最有价值的不是对答案而是把它当成一面镜子照出自己知识体系里的漏洞。我结合自己的经验建议从下面几个方向去做针对性准备。4.1 数据结构和算法建立“题感”而不是背题如果你准备时间有限先把高频考点过一遍链表操作反转、合并、环检测、二叉树遍历、层序、最近公共祖先、排序快排、归并的代码要能默写、二分查找含各种变体、动态规划背包、LIS、LCS、字符串KMP、滑动窗口、正则匹配。但不要满足于“会做某一道题”要总结出“这一类题的共同模型”。比如看到“求最大值/最小值/方案数”且满足“当前状态可以从之前的状态转移得到”优先想DP看到“在有序数组里找某个值”优先想二分看到“最近/最短”且是图结构优先想BFS。这种从题目特征到算法模型的映射能力就是所谓的“题感”。4.2 计算机基础不能只背八股文操作系统、网络、数据库这三大块光靠背诵“什么是死锁”“什么是三次握手”是拿不到高分的因为笔试越来越喜欢考“变着法儿的问法”。比如问你“TCP的TIME_WAIT状态过多怎么排查和优化”表面考状态实际考你对网络编程和系统调优的实践经验。我发现一个好方法是用“自问自答”的方式检查自己是不是真理解讲完一个概念后立刻问自己三个问题——它解决了什么问题没有它会怎样它的局限性是什么如果三个问题都能用自己的话回答出来说明是真懂否则就是在背书。4.3 编程能力多手写少依赖IDE笔试环境通常没有自动补全和编译器报错所有问题都得靠眼睛看出来。所以平时练习时尽量切换到“无IDE模式”——在文本编辑器里写代码或者直接在纸上写。写完之后不要急着跑测试用例先自己用眼睛“执行”一遍代码逐行走一遍逻辑看有没有越界、空指针、死循环的风险。这里分享一个我常用的检查清单输入为空时代码会不会崩溃只有一个元素时结果对不对数据量最大时会不会超时或超内存数值会不会溢出有没有重复计算导致不必要的性能损耗把这些变成肌肉记忆之后笔试现场漏写边界条件的概率会大大降低。4.4 概率与逻辑用“反面”思维提高效率逻辑题里有很多“至少/至多”的表述这类题用反面求解往往比正面快得多。比如“掷两颗骰子点数之和至少为6的概率”正面的情况很多先算出“点数之和小于6”的概率用1一减就出来了能节省不少时间。排列组合题也要注意“有序/无序”“放回/不放回”的区分。读题时圈出关键词别凭感觉套公式。做完之后如果时间充裕可以尝试用枚举验证一下小规模的情况防止公式带错。5. 后续提升建议把笔试当面试的敲门砖而不是终点最后再聊一个我在带新人时经常强调的点笔试不是目的通过笔试进面试才是开始。这套卷子里涉及的每个考点面试中大概率都会被追问。比如你笔试写了动态规划面试官就会问你“时间复杂度能不能优化”“滚动数组的原理是什么”“如果加一个限制条件怎么变通”。所以笔试之后建议针对错题和不确定的题目把背后的知识点再深挖一层相当于提前做了一轮面试准备。我当时做完这份题之后做的事情是把所有错题整理成一个表格内容包括“题目”、“错误原因”、“正确思路”、“关联知识点”、“面试可能追问方向”五列。然后针对每一行去补充资料。这个习惯我保留至今它帮我建立了一个很扎实的基础知识网络。另外建议大家在准备过程中保持输出的习惯。每学完一个知识点用自己的话写一小段总结或者讲给朋友听。我的经验是凡是能讲清楚的知识点才真正内化成了自己的凡是讲不清楚的哪怕当时觉得听懂了考场上大概率还是会掉链子。现在再回头看看这份京东2016研发工程师笔试题虽然已经过去了几年但它的出题质量和考察逻辑放到今天依然相当能打。它不像一些所谓的“刷题冲刺卷”那样偏离实际也不像纯算法竞赛那样劝退而是非常精准地打在了一个合格研发工程师的核心知识边界上。对于正在准备笔试的人这确实是一份值得反复咀嚼的经典材料。如果你已经把这份题刷完了我个人建议不要停下来接着去找同级别公司的其他年份真题继续练手。做题这件事量变引起质变但前提是每次做完之后要有复盘有总结有输出。最后祝你笔试顺利能靠这份积累拿下面试。