尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

2016京东研发笔试题复盘:数据结构与算法考点精讲

2016京东研发笔试题复盘:数据结构与算法考点精讲 2016年的京东研发工程师笔试题放到今天翻出来看依然很有嚼头。那会儿互联网校招笔试正处于“选择题海量刷 在线编程题卡时间”的阶段京东这套题无论是题型结构还是考点覆盖都相当有代表性数据结构、算法、操作系统、网络基础一个不落编程题里还藏着几道“看着简单、一写就错”的经典题目。我这次把这套题重新梳理了一遍不想只给你贴答案而是想聊聊每道题背后的考查意图、解题思路以及当年我在这类笔试里踩过的坑。不管你是正在准备校招的应届生还是想查漏补缺的社招选手这篇内容应该都能给你一些实在的参考。1. 笔试题目背后的考察逻辑1.1 一份典型试卷的结构2016年前后的研发类笔试普遍采用“客观题 编程题”的组合方式京东也不例外。客观题部分通常覆盖计算机基础四件套数据结构、操作系统、计算机网络、C/Java语言特性偶尔穿插一两道数据库或Linux命令题。编程题一般是两到三道题目不会特别偏门但很考验基本功是否扎实。这里有一个容易被忽略的信息客观题的题量通常不小大约在30到50道之间而笔试总时长一般是90到120分钟。这意味着你平均每道选择题只有两分钟左右如果某道题卡住了最优策略不是死磕而是先标记、后回看。我自己当年就吃过亏在一道TCP状态流转的题上耗了快十分钟结果后面几道计算机网络题全凭感觉蒙编程题也差点没写完。从整体考察逻辑来看这套题想筛选的并不是“刷题量最大的人”而是“基础扎实 能在限定时间内写出可用代码的人”。所以你会发现客观题里大量出现时间复杂度比较、排序算法稳定性、二叉树遍历顺序这类“死记硬背容易忘、理解了就忘不掉”的知识点。1.2 高频考点背后的“能力信号”京东作为电商平台后端流量大、并发高研发工程师日常要处理大量海量数据排序、缓存淘汰、接口幂等等问题。笔试考点其实就是这些业务场景的缩影。举个例子排序算法在客观题里出现频率极高比如问快排最坏时间复杂度、堆排序是否稳定、归并排序的空间复杂度。这些知识点本身不难但很多人只是背了结论没有真正理解稳定性在真实场景中的意义。所谓排序稳定性是指值相等的元素在排序后是否保持原有相对顺序。这在电商场景里非常关键比如先按成交量排序再按价格排序如果第二次排序不稳定之前成交量排序的结果就被打乱了。再比如二叉树相关的题目表面上考遍历实际上是在考查递归思维的熟练度。我见过不少候选人笔试能写出前中后序遍历的递归版本一旦要求用迭代实现、或者让你根据前序和中序重建二叉树就开始混乱。这说明他们对递归的理解停留在“背模板”层面没有真正建立“函数调用栈”的心智模型。所以复习这套题的时候我建议你把每一道题都当成一个“信号”它考的不是这道题本身而是背后那个知识模块你是否真正掌握。2. 必考数据结构题解析用两个栈实现队列2.1 题目描述与核心思路这道题几乎是2016年前后各厂笔试的“常驻嘉宾”京东也考了。题目很简洁用两个栈实现一个队列需要支持push、pop、peek、empty四个操作。初次看到这个题最容易产生的疑问是栈是先进后出队列是先进先出这不是矛盾吗其实关键在于“利用两次先进后出抵消成先进先出”。思路是准备两个栈一个管入队一个管出队。入队时直接往第一个栈里压出队时如果第二个栈为空就把第一个栈的所有元素依次弹出并压入第二个栈然后从第二个栈弹出栈顶元素。如果第二个栈不为空直接弹出栈顶即可。这里有一个细节值得强调只有当第二个栈为空时才执行“倒腾”操作而不是每次出队都倒腾。如果每次pop都把第一个栈的元素全部搬到第二个栈那连续pop两次时第一次搬完、第二次直接弹第二个栈的栈顶就好了不需要重复搬运。这个“暂时不用动、用完了再补货”的思路和CPU缓存、Redis持久化里的延迟写思想有异曲同工之处。2.2 代码实现与复杂度分析用一个C实现来直观展示class MyQueue { private: stackint inStack; stackint outStack; public: void push(int x) { inStack.push(x); } int pop() { if (outStack.empty()) { while (!inStack.empty()) { outStack.push(inStack.top()); inStack.pop(); } } int top outStack.top(); outStack.pop(); return top; } int peek() { if (outStack.empty()) { while (!inStack.empty()) { outStack.push(inStack.top()); inStack.pop(); } } return outStack.top(); } bool empty() { return inStack.empty() outStack.empty(); } };复杂度方面push操作是严格的O(1)。pop和peek操作最坏情况下需要搬运n个元素单次是O(n)但每个元素最多只会被“倒腾”两次一次进inStack、一次进outStack所以均摊下来依然是O(1)。这个均摊分析思路在笔试面试中经常被追问建议你把推导过程牢牢记住。2.3 实操中容易忽略的细节我在反复练习这道题时总结了几个容易扣分的点。第一pop和peek的逻辑不能各自为政。很多人的第一版代码会在pop里写一遍“倒腾”逻辑然后在peek里又复制一遍看似没问题其实如果后续需要修改数据结构很容易改漏一处。更优雅的做法是封装一个辅助函数专门负责“确保outStack里有元素”然后pop和peek都调用它。第二边界条件要考虑清楚。如果队列为空时调用pop或peek程序应该怎么表现笔试场景下可以抛出异常也可以返回一个特殊值但一定要在代码里体现出来而不是放任不管。我在真实笔试里见过有人没写这个判断结果样例一跑直接崩了。第三面试官通常会追问“能不能用两个队列实现栈”这道变体题思路类似核心是入栈时把新元素放进空队列然后把另一个队列的所有元素依次转移过来这样新元素永远在队列头部。3. 经典算法题拆解数组中第K大的数3.1 题目分析与暴力解法“找出数组中第K大的数”这类题目在京东笔试中属于典型的“一题多解”题型。题目本身不复杂但解法选择直接反映候选人对算法复杂度的敏感度。最直观的解法是先把数组排序然后按下标取第K大的元素。用C的sort时间复杂度是O(n log n)空间复杂度O(1)。这个解法在笔试时可以用来保底但如果你只写出这一种解法很难拉开差距。因为出题人想看到的不是“你会排序”而是“你能否在O(n)时间复杂度内解决问题”。另一种思路是维护一个大小为K的最小堆遍历数组时如果堆不满就直接入堆否则比较当前元素和堆顶元素如果当前元素更大就弹出堆顶、把当前元素入堆。遍历结束后堆顶就是第K大的元素。时间复杂度是O(n log K)当K远小于n时这个方案比排序更高效而且能处理数据流式输入的场景。3.2 快速选择算法与完整代码如果想做到平均O(n)的时间复杂度需要用快速选择算法它在思想上是快排的“减治”版本。快排每次partition会把数组分成两部分左边小于等于pivot右边大于等于pivot。快速选择不去递归处理两边而是根据pivot的位置判断目标元素在哪一侧只递归处理那一侧。求第K大可以转换成求第n-K1小这样和partition的分区方向保持一致不容易搞混。完整实现如下int partition(vectorint nums, int left, int right) { int pivot nums[left]; int i left, j right; while (i j) { while (i j nums[j] pivot) j--; nums[i] nums[j]; while (i j nums[i] pivot) i; nums[j] nums[i]; } nums[i] pivot; return i; } int quickSelect(vectorint nums, int left, int right, int k) { if (left right) return nums[left]; int pos partition(nums, left, right); int rank pos - left 1; if (rank k) { return nums[pos]; } else if (rank k) { return quickSelect(nums, left, pos - 1, k); } else { return quickSelect(nums, pos 1, right, k - rank); } } int findKthLargest(vectorint nums, int k) { int n nums.size(); return quickSelect(nums, 0, n - 1, n - k 1); }partition里有一个非常容易写错的点外层while循环中两个内层循环的条件判断顺序不能颠倒必须先从右往左找小于pivot的元素再从左往右找大于pivot的元素。如果反过来最后交换pivot的位置时可能会出错。我在练习时反复踩过这个坑后来养成习惯先写注释标明“从右向左找第一个小于pivot的位置”再动手写代码。3.3 复杂度对比与边界处理三种解法放在一起对比会更直观解法时间复杂度空间复杂度适用场景排序后取下标O(n log n)O(1)代码量最小适合快速验证大小为K的最小堆O(n log K)O(K)适合海量数据、流式数据K较小时优势明显快速选择平均O(n)最坏O(n^2)O(log n)性能最优适合数组一次性给出但最坏情况需要优化边界处理方面有三点需要特别注意。第一k的取值范围必须是1到n如果k小于1或大于n直接返回错误提示不要走进递归逻辑。第二数组里存在大量重复元素时快速选择的效率会下降因为partition可能每次都把数组切成极不均衡的两半。第三当n等于1时quickSelect的递归终止条件直接生效无需partition。如果面试官继续追问“最坏情况怎么优化”你可以回答在partition之前随机选择一个元素作为pivot或者用“取首元素、中间元素、尾元素的中位数”作为pivot这样可以把最坏情况出现的概率降到极低。4. 字符串全排列与递归回溯4.1 题目分析与递归思路字符串全排列是笔试递归题里的“试金石”京东这套题也出现过类似变体。题目要求给定一个可能包含重复字符的字符串输出它的所有不重复全排列。递归思路的核心是“固定当前位置交换后续元素”。假设字符串长度为n我们递归处理位置idx当idx等于n时说明已经排满记录当前结果。否则遍历从idx到n-1的所有位置把每个位置的字符交换到idx然后递归处理idx1。这里有一个关键点交换之后递归返回时必须再次交换回来否则会破坏原来的字符串状态。这个“回溯”动作是整个递归回溯法的灵魂很多人写全排列代码时漏掉这一步结果输出结果莫名其妙地混乱。4.2 去重处理与剪枝优化如果字符串中有重复字符直接按上述思路会生成大量重复排列。比如“aab”的全排列如果不去重会得到6个结果但实际只有3个不重复排列。去重的方法有很多种我比较推荐在交换前做一个“当前位置是否出现过相同字符”的判断遍历从idx到i-1的区间如果发现某个字符和当前位置的字符相同就跳过这次交换。这个判断的思路是同一个字符在idx位置已经被处理过一次了再处理一次只会生成重复结果。代码实现如下void dfs(string s, int idx, vectorstring result) { if (idx s.size()) { result.push_back(s); return; } for (int i idx; i s.size(); i) { bool duplicate false; for (int j idx; j i; j) { if (s[j] s[i]) { duplicate true; break; } } if (duplicate) continue; swap(s[idx], s[i]); dfs(s, idx 1, result); swap(s[idx], s[i]); } }这种去重方式比“先用set记录所有结果、最后再去重”更高效因为它在生成过程中就剪掉了重复分支而不是等所有结果都生成后再过滤。4.3 常见错误和调试心得我在调试全排列代码时总结出几个高频错误。第一个错误是递归终止条件写错。有人会用“i s.size()”作为终止条件但递归参数是idx终止条件必须和递归参数对应。写代码时一定要想清楚“这个参数代表什么含义”。第二个错误是忘了恢复现场。前面提到过交换之后必须交换回来这是回溯法区别于普通递归的关键特征。我在笔试现场曾经因为紧张漏掉这一行调试了十分钟才发现问题。第三个错误是去重判断的区间写错。应该从j idx开始而不是从j 0开始因为我们要检查的是“当前位置idx到i-1之间是否出现过s[i]”而不是“整个字符串之前是否出现过”。另外给你一个调试建议写递归题目时可以先在小规模输入上手动模拟一遍递归过程。比如输入“abc”在纸上画出递归树标出每次交换后的状态这样能帮你快速发现逻辑错误比在IDE里一步步调试高效得多。5. 笔试现场的时间分配与答题策略5.1 客观题的做题节奏整套笔试的时间分配很大程度上决定了你能不能在编程题上拿到分。我的建议是客观题部分控制在60到70分钟以内给每道编程题留出至少20分钟的编码和调试时间。客观题里遇到完全没思路的题不要恋战先随便选一个你觉得最有可能的选项然后在草稿纸上记下题号。等所有题目做完一遍如果还有剩余时间再回头仔细推敲。这看起来是常识但我在真实笔试中见过太多人在一道题上卡了十分钟导致后面节奏全乱。另外客观题里经常出现“不定项选择”或多选题这种题的计分规则通常是“全部选对才得分”所以拿不准的选项宁可少选也不要多选。我当时在这类题上的策略是只选100%确定的选项但凡有一点犹豫直接放弃。5.2 编程题的踩分技巧编程题的踩分核心原则是“先跑通再优化”。哪怕你只能写出一个暴力解只要它能通过部分测试用例就能拿到对应分值。很多在线笔试系统会按通过的测试点比例给分不要因为题目要求O(n)解法就直接放弃先用O(n^2)的暴力解把基础分拿住再说。具体操作上我建议你按照下面这个顺序来先读题划出输入范围、输出格式、边界条件。在代码里先写出函数签名和整体框架注释好每一步要做什么。实现一个最朴素的正确解法不管是暴力还是低效算法先让程序能跑。跑题目给的示例输入确认输出正确。有剩余时间再在当前解法基础上优化或者换更优算法。还有一个小技巧笔试环境里一旦提交代码通常会立刻返回部分测试用例的结果。如果某个测试点没过不要慌尝试用极端输入检查代码比如空数组、只有一个元素、最大数值、重复元素等。这些边界情况往往就是隐藏测试点的来源。6. 从笔试到面试知识点的延伸准备6.1 高频易错点速查表我把这套题里涉及的易错知识点整理成了一张速查表你可以打印出来贴在电脑前笔试前快速过一遍知识点易错点记忆口诀栈和队列双栈实现队列时只有出栈为空才倒腾用完了再补货快速选择partition的while循环判断顺序先右后左覆盖式赋值全排列去重去重判断区间从idx开始不是从0开始只查本层区间递归回溯回溯时必须恢复现场交换了就要换回来排序稳定性快排不稳定、归并稳定、堆排不稳定选主元、落位置、会交换就是不稳定二叉树遍历前中后序的迭代实现显式栈模拟递归栈TCP状态迁移主动关闭方经历TIME_WAIT先FIN后ACK等2MSLC内存管理栈内存自动释放、堆内存手动释放栈归栈管堆归人管6.2 顺着真题延伸复习路径笔试只是第一关接下来还有一轮甚至多轮面试。这套真题里涉及的考点其实可以串成一条完整的复习路线按顺序推进会让你事半功倍。第一优先级是数据结构和基础算法。栈、队列、链表、二叉树、排序、二分查找、双指针这些知识点不仅笔试常考面试手撕代码环节也基本绕不开。建议你把这些核心数据结构的“手写实现”都过一遍不只是会调用API而是真的能默写出代码。第二优先级是高频题型专项突破。动态规划、回溯法、贪心算法、深度优先搜索、广度优先搜索这五类题型几乎占据了大厂算法面试的80%。每类题型找10道左右的经典题目刷完以后总结出通用的模板和套路。第三优先级是计算机基础八股。操作系统里的进程线程、内存管理、死锁计算机网络里的TCP/UDP、HTTP、DNS数据库里的索引、事务、隔离级别这些是面试官最习惯发起提问的领域。建议用费曼学习法把每个知识点尝试讲给一个完全不懂的人听讲不清楚的地方就是你的知识盲区。最后想说的是这套2016年的京东笔试题虽然年代有些久远但其中考察的基本功到现在依然是各厂研发岗招聘的重心。我后来参与过一些技术面试的命题工作发现出题人的思路其实一直没变不追求偏题怪题而是用最经典的题目筛查出基础最扎实、代码能力最过硬的候选人。所以刷这套题的时候与其追求“刷完所有题”不如把每道题背后的知识模块彻底搞懂。我自己当年笔试时在“两个栈实现队列”这道题上卡了很久后来彻底弄懂了设计思路再遇到同类型的变体题都轻松不少。希望这篇复盘能帮你少走一些弯路把时间花在真正重要的地方。
返回列表