ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

人人网2015研发笔试卷深度解析:经典题型与备考策略

人人网2015研发笔试卷深度解析:经典题型与备考策略 我在整理本地资料的时候,翻出一份“人人网2015研发笔试卷A”的扫描版。那会儿人人网的校园社交和游戏业务还在持续招人,研发岗位的笔试基本还是“线下教室发卷、两小时收卷、白纸手写代码”的流程。现在回看这份卷子,不只是怀旧,它其实是个很好的切片:能看出2015年一家中型互联网公司对“研发工程师”这个岗位的核心预期是什么,也能和今天动辄三小时在线IDE、多轮算法面的校招流程做个对比。这篇文章适合三类人:正在准备笔试的应届生,可以从里面看到经典的出题思路和解题框架;做过面试官、出过题的人,可以对照一下现在的招聘标准变了多少;纯粹好奇“当年互联网公司笔试到底考什么”的读者,也能从中得到一些有意思的细节。我尽量以当年的出题风格为基准,把常见的题型、解题思路和复习方法串起来讲,而不是只贴一份干巴巴的题目答案。1. 一张2015年的卷子,为什么现在还值得翻出来重做1.1 那年头笔试的“共识”和“潜规则”2015年的技术栈和今天差别挺大:后端主流是Java MySQL Redis Memcached,前端还在jQuery时代,移动端处于Android/iOS原生开发最吃香的阶段。笔试出题人的逻辑其实很简单:先筛掉不能手写基础算法的人,再筛掉只有理论不懂实战的人。所以那时候的笔试卷子里,“手写快速排序”“手写单例模式”“手写链表反转”几乎成了标配题型。这份人人网2015研发笔试卷A的整体风格,和当年各大互联网公司的出题方向高度一致:没有太多偏题怪题,追求的是“稳定筛选出能把代码写对的人”。这里有个绕不开的技术背景——2015年前后,候选人简历里普遍写着“熟悉Java”“熟悉数据结构”,但真正拉出来现场手写代码的时候,能一次写对的人不多。所以笔试承担的核心任务是“现场验证”,而不是“知识竞赛”。如果你以为这份卷子是想考倒人,那就理解偏了。它更像是一个漏斗,把“背过八股文但没写过代码”的人先漏下去,留下那些拿到题目就能进入状态的人。这种出题思路到今天也没有过时,只是考察载体从纸质卷子换成了在线评测系统。1.2 从A/B卷设置看判分逻辑“笔试卷A”后面这个字母A,说明当时至少还有一份B卷。通常A/B卷的题目不完全一样,但难度会做对齐,主要目的是防止相邻座位的考生互相“借鉴”。这在纸质笔试时代是常规操作,尤其是大教室统一开考的场景下,A/B卷加不同答题顺序,基本能把抄袭成本拉到很高。从判分角度来说,研发笔试的逻辑和学校考试不太一样。学校考试追求“60分及格,90分优秀”,但企业笔试本质上是在做排序,它不关心你考了多少分,只关心你在所有候选人里排第几。所以有些题目的分值比例看起来不合理,其实是故意的。举个例子:一道20分的算法大题,可能有超过一半的人拿不到完整分数,但出题人依然会把它放在卷子里,因为这道题能有效区分“思路清晰的候选人”和“基础不牢的候选人”。我在实际参与过类似的阅卷工作之后,有个很深的体会:笔试判卷不是按“得分点”逐条抠的,而是先分档,再看细节。一份卷子如果整体思路清晰、代码风格干净,即使有小错误,也会被归到“可进入面试”那一档;反过来,如果思路混乱、代码涂改严重,即使最后结果对了,也容易被判定为“背题或蒙题”。这个细节对后来准备笔试的人很有价值——卷面本身就是你在没有对话的情况下,向面试官传递“我这个人写代码是什么风格”的唯一渠道。1.3 试卷背后那批候选人画像当年投人人网研发岗的,以应届本科生和硕士生为主,也有少量两三年经验的社招候选人。笔试考察的核心不是“知识面广”,而是“有没有完整学过计算机基础、有没有真正写过代码”。一个很典型的现象是:很多候选人能滔滔不绝地讲HashMap的原理,但让他手写一个基于链表的LRU缓存就卡住了。这背后的原因不是他不懂LRU,而是他平时只看题解、不亲手实现,导致真正动笔的时候,对指针操作、边界条件、泛型语法都生疏了。这份卷子本质上就是在筛选“真正写过代码、练过题”的人,而不是“看过很多题”的人。还有一个值得注意的点:2015年的笔试普遍没有“系统设计”的专项大题,最多在简答题里带一两个“如何设计一个XX”的问题,分值占比不高。这跟当时招聘的岗位层级有关——校招和初中级社招更看重基础能力,系统设计通常留到面试环节再考察。现在很多公司把系统设计提前到笔试阶段,其实是招聘流程变长、竞争变激烈后的产物。2. 当年研发笔试题的四大出题方向2.1 数据结构与算法:占比最大的一关任何一份2015年的研发笔试卷,数据结构与算法都占了最大比重,这份A卷也不例外。题型分布上基本延续了当年的通用套路:选择题:时间复杂度/空间复杂度比较、二叉树性质、排序算法稳定性、哈希冲突处理方法、图的基础概念。大题:手写链表反转、手写二叉树遍历(尤其是非递归版)、字符串匹配、简单的动态规划(如最长公共子序列、背包问题)。动态规划在当年非常受欢迎,原因很简单:它能在半小时内看出一个人的抽象能力和状态定义能力。一道经典的DP题,暴力递归和动态规划之间的代码差距,能直接反映候选人有没有真正理解“用空间换时间”的本质。如果你现在正在准备笔试,我的建议是:不要因为这套题“老了”就跳过。数据结构与算法的核心考察点——复杂度分析、边界条件处理、递归与迭代的转换——到今天依然是所有技术面的基石。题型会变,但能力考察的内核没变。2.2 操作系统与网络:区分“背题党”和“真懂”的试金石操作系统和计算机网络在笔试里通常以选择题和简答题的形式出现,考察点集中在:进程与线程的区别、死锁产生的四个必要条件、虚拟内存与页面置换、TCP三次握手与四次挥手、TIME_WAIT状态的意义。这里要特别说一句:单纯知道“三次握手是SYN、SYNACK、ACK”不算懂网络。2015年的出题人已经学会把知识点场景化了。比如“一个服务大量出现TIME_WAIT连接,可能是什么原因?怎么处理?”这种题,能把“背过TCP状态机”和“真正处理过线上连接问题”的人清楚地区分开。为什么这类题能区分“背题党”?因为操作系统和网络都是实践性极强的学科。你看书时记住了“死锁四个条件”,但没在代码里见过锁的顺序问题,遇到“怎么避免死锁”的开放题就会露馅。笔试里的这类题目,表面考的是知识点,实际考的是你有没有在真实环境里遇到过这些问题。2.3 数据库与工程实践:考察能不能直接上手干活数据库在研发笔试中从来不是配角。这份A卷里,SQL题基本是必考的,常见形式是:给出两张表(比如用户表和订单表),要求写一个JOIN查询,或者带GROUP BY和HAVING的聚合查询。还会考索引失效的场景、事务隔离级别、乐观锁与悲观锁的区别。这部分题目的区分度很高。有人能写出语法正确的SQL,但问他“为什么要给这个字段建联合索引”就答不上来;有人能把索引的B树原理讲得很透,但让他手写一条稍微复杂的SQL就写得七扭八歪。真正优秀的候选人,两者都能兼顾。工程实践类题目还包括一些Java生态的常识题,比如HashMap在并发下的问题、volatile关键字的作用、线程池的参数含义。这些题目在今天看来依然是高频考点,但当年的考察方式更偏向“记结论”,现在的考察方式更偏向“让你在并发场景里做设计选择”。这也是技术面试整体从“知识记忆”向“能力应用”迁移的一个缩影。2.4 开放设计题:看你有没有架构意识开放设计题在当年的笔试里分值不高,但很能看出潜力。常见的形式是:“设计一个短网址系统”“设计一个关注/粉丝关系的存储结构”“设计一个带过期时间的缓存”。这类题没有标准答案,考察的是候选人面对模糊需求时的反应。没做过系统设计的人也能扯几句,比如“用一张表存短网址和长网址的映射”,但能否进一步想到“需要加缓存”“需要考虑哈希冲突”“需要支持过期清理”“需要考虑并发下的重复创建”,分数就会拉开差距。2015年的开放设计题远没有今天这么卷。现在的系统设计题已经细化到“设计一个支持千万QPS的短网址服务”,要求候选人给出容量估算、分库分表方案、缓存策略、可用性设计。但当年,你只要提到“用Redis做缓存”“对原始URL做MD5后取前几位”“用发号器保证ID唯一”,就已经是相当不错的回答了。这跟行业整体发展水平有关,也跟当年招聘岗位的职级预期有关。3. 挑几道典型题,把当时的思考过程走一遍这份卷子的具体原题我记不全了,但结合当年的出题风格和后来流出的面经,有几类题目出现的概率极高。我按“拿到题之后应该怎么想”的方式,把完整的解题链路走一遍,而不是直接甩答案。3.1 一道数组类题目的多种解法:最大子数组和“给定一个整数数组,找到一个具有最大和的连续子数组,返回其最大和。”这道题在2015年出现得非常频繁,原因在于它有清晰的难度梯度。第一步,暴力解。三层循环枚举起点、终点,再求和,时间复杂度O(n^3)。笔试时间充裕的话,可以先写这个保底,确保有分。第二步,前缀和优化。用前缀和数组把求和时间降到O(1),整体复杂度O(n^2)。第三步,动态规划。定义dp[i]为以i结尾的最大子数组和,状态转移方程是dp[i] max(nums[i], dp[i-1] nums[i]),时间复杂度O(n),空间复杂度可以压缩到O(1)。这道题我为什么印象深?因为很多候选人死磕动态规划,反而忽略了“先把暴力解写出来”的重要性。在笔试里,一道20分的大题,你写出O(n^3)的暴力解能拿6-8分,写出O(n^2)能拿12分左右,写出O(n)的DP直接满分。但如果你卡在“我要直接写出最优解”上,半小时一个字没写,那这道题就是0分。笔试策略永远是:先拿保底分,再冲满分。这也是我在实际阅卷时最想告诉候选人的一件事。3.2 一道链表题目的边界条件陷阱:两两交换链表中的节点“给定一个链表,两两交换其中相邻的节点,并返回交换后的链表。”这道题在当年的笔试里也很常见,因为它考察的不是“会不会交换”,而是“能不能处理清楚边界”。链表的边界条件无非这么几个:空链表、只有一个节点、奇数个节点、偶数个节点。很多人写代码时,主逻辑是对的,但漏掉了“只有一个节点时直接返回”这个判断,导致整个代码在边界输入上报错。手写代码不像在线IDE,没有测试用例帮你发现问题,所以只能在写的过程中自己给自己“造测试用例”。我的建议是:看到链表题,先在心里默念三遍“空指针、单节点、尾节点”。这三个边界处理清楚,链表题基本就拿下一大半了。还有一个实用技巧:用“哑节点”(dummy node)统一处理头节点被修改的情况。这个技巧在笔试里能少写很多if判断,而且卷面上看起来思路更清爽。非递归的二叉树中序遍历也是同一类题,考察点同样是“怎么用显式栈模拟递归过程”,边界条件处理好了,思路对了,分数就拿到了。3.3 一道排序/查找题从O(n^2)到O(n)的优化路径:数组中的第K大元素“在未排序的数组中找到第K大的元素。”这道题在2015年也是高频题,它的难度梯度同样很清晰。最直接的做法:调用Arrays.sort()排序,然后取倒数第K个,时间复杂度O(n log n)。这个解法能拿基础分。进阶做法:基于快速排序的partition操作,每次可以把数组分成两部分,根据K落在哪一部分决定递归方向。平均时间复杂度O(n),最坏O(n^2)。更进阶的做法:用大小为K的最小堆维护当前最大的K个数,时间复杂度O(n log K),适合处理数据量很大、不能全量放入内存的场景。笔试中,你能写出排序法已经能超过一半的人;能写出partition的优化版本,基本就能拿到这道题的优秀分。这个题给后来人的启发是:优化不是一步到位的,而是在“当前解法能跑通”的基础上,一步步拆解瓶颈,找到可以改进的点。这种思维方式比背下“第K大用堆”这个结论重要得多。3.4 一道开放题的拆解思路:设计一个短网址系统当年如果考开放设计题,大概率会出现“设计一个短网址系统”。作为候选人,拿到这道题不要慌,更不要直接上手写表结构。标准思考路径是:先问需求:短网址的长度要求?每天新增多少条?总数据量多大?需要支持自定义短码吗?然后估容量:假设每天新增100万条,一年就是3.65亿条,MySQL单表能扛,但如果要做高可用,需要主从复制。接着设计核心流程:生成短码 → 存储映射 → 重定向时查表 → 302跳转到原网址。最后考虑优化:热点网址加Redis缓存、短码生成用发号器避免哈希冲突、定期清理过期记录。2015年的校招笔试,你能答出“用MD5或MurmurHash生成短码”“存MySQL”“加缓存”这三点,已经是很完整的答案了。如果能写到“发号器方案”“考虑缓存穿透”,那就是加分项。现在回看这道题,它考察的核心能力其实是“分解问题”的能力——把一个模糊的开放问题,拆成需求、容量、流程、优化几个模块,再逐个击破。这个思路到今天做任何系统设计题都通用。4. 用现在的眼光回看,哪些题过时了,哪些题依然能打4.1 语言特性题:Java 7时代的东西今天还适用吗2015年的笔试里,Java题是重头戏,尤其是HashMap在并发下的问题、volatile的可见性、synchronized和Lock的区别。这些题目本身没有过时,但考察方式已经变了。当年的典型考法是:“HashMap为什么线程不安全?多线程下会出现什么问题?”你只要答出“JDK7中头插法可能导致循环链表,JDK8中putIfAbsent的复合操作非原子”就OK了。现在的考法更偏向“ConcurrentHashMap在JDK8里为什么放弃分段锁改用了CAS synchronized”“LongAdder和AtomicLong的适用场景是什么”。同样是考察并发基础,现在的问题明显更贴近实际工程选型。语言特性题的变化,本质上是Java生态演进的缩影。JDK 8的Stream、Lambda、CompletableFuture,JDK 11的ZGC,JDK 17的密封类……每一个新特性都会催生新的面试题。但底层的并发模型、内存模型、类加载机制,这些“不变的东西”反而更值得花时间吃透。因为面试官考察语言特性的真正目的,从来不是让你背API,而是看你对语言底层机制的掌握程度。4.2 考点背后的真实能力模型把这份卷子里的考点做一个映射,会得到一张很有价值的能力表:数据结构与算法题:考察逻辑思维、边界意识、复杂度分析能力。操作系统题:考察对资源调度、并发控制、内存管理的理解深度。网络题:考察对真实系统通信过程的掌握程度,以及排查线上问题的经验。数据库题:考察数据建模能力、索引设计意识、SQL熟练度。开放设计题:考察需求分析能力、架构取舍意识、表达能力。这五项能力放到今天的研发岗位面试里,依然一个都不过时。只是考察载体变得更复杂了:算法题从“手写反转链表”变成了“在线IDE里30分钟AC一道LeetCode中等题”;系统设计题从“设计短网址”变成了“设计一个支持千万并发的消息队列”。但内核没变,还是看候选人能不能在限定时间内,用清晰的思路解决一个定义明确或模糊的问题。所以,如果你现在准备笔试,与其焦虑“LeetCode刷不到500题怎么办”,不如先问问自己:这道题背后考察的能力模型是什么?我是真的理解了,还是在背题?这个问题的答案,往往决定了你在真正面试时能走多远。4.3 从这套卷子看面试准备策略的变与不变变的部分很明显:现在的候选人需要懂分布式理论,至少知道CAP定理、负载均衡、缓存一致性、消息队列的基本原理;需要会用主流框架,Spring Boot、MyBatis、Redis这些几乎变成简历标配;需要刷LeetCode,因为笔试题库已经全面在线化,题目难度也水涨船高。不变的部分更有意思:不管是2015年还是2025年,手写代码的基本功、对底层原理的理解、清晰的表达能力和逻辑思维,永远是面试官最看重的底层素质。框架可以学,工具可以换,但一个候选人能不能把“复杂问题拆解成简单步骤”并用代码实现出来,这个判断标准十年没变过。这些年我见过不少候选人,简历写得非常漂亮,项目经验里堆满了各种新名词,但一到手写代码环节就露怯。反过来,也见过学校背景一般、项目经验朴素的候选人,因为算法基础扎实、表达清晰,最后拿到了不错的offer。技术面试本质上是“去除包装、看真实能力”的过程,这一点在2015年如此,在今天依然如此。5. 如果让我重新准备这份卷子,我会怎么做5.1 先拿真题做时间模拟,别只看题解准备笔试最容易踩的坑,就是“看题解觉得自己会了,一上手就卡住”。看题解带来的“我已经掌握了”的错觉,比不会更危险,因为它会直接拉高你在真实笔试时的预期,然后摔得很惨。正确做法是:找一套模拟题(不一定是这份2015年的,任何一套真题都可以),定好两小时倒计时,准备白纸和笔,关掉所有资料和IDE,完全模拟真实笔试环境。写完对照答案批改,记录哪些题卡了多久、哪些题边界条件漏了、哪些题复杂度分析错了。这种模拟做三套以上,你就知道自己的真实水平在哪里,以及时间分配该怎么调整。手写代码这件事,平时在IDE里写和考场手写完全是两种体验。IDE有自动补全、有编译报错、可以随时调试,手写什么都没有。所以练习时一定要刻意“手写”,至少把每道题的完整代码在白纸上或记事本里敲一遍,训练“一次性写对”的能力。5.2 把错题按考点分类,集中突破很多人刷题是“今天刷一道数组,明天刷一道字符串,后天刷一道树”,看起来很努力,但效果很差。原因是这种刷法没有形成知识闭环,每道题都是孤立的,下次遇到同类题还是不会。更高效的策略是:每做完一套题,把错题按考点分类。比如“数组类题目:错误原因是边界条件没考虑”“链表类题目:错误原因是不会用哑节点处理头节点”“DP类题目:错误原因是状态定义不清晰”。然后针对出错最多的考点,集中刷20道同类题,直到把这类题的错误模式彻底修正。这个方法和“刻意练习”的核心思想一致:只在舒适区边缘反复练习,才能最快地提升能力。盲目刷题是重复劳动,针对性突破才是有效练习。我当年带过的不少学弟学妹,用这个分类方法准备,基本三到四周就能把笔试通过率提升一个档次。5.3 笔试是策略题:学会放弃最后这点特别重要,但很少有人讲:笔试不只是能力测试,还是策略游戏。一套卷子两个小时,如果在一道选择题上卡了五分钟,在一道大题上卡了二十分钟,整体节奏就被带崩了。正确的策略是先扫一遍整张卷子,快速判断每道题的难度和分值,然后按“先易后难、先高分后低分”的顺序作答。遇到卡壳超过五分钟的题,先跳过去做后面的,最后有时间再回头想。很多人在笔试里不是不会做,而是死磕一道题导致后面的大题没时间写,这是最可惜的。还有一个容易被忽略的细节:手写代码时,先写思路注释,再写代码。比如“// 1. 先处理空指针边界” “// 2. 用双指针遍历” “// 3. 返回结果”。这样做有两个好处:一是给自己的思考过程留出空间,不会写着写着忘了思路;二是阅卷时即使代码有小错,面试官看到清晰的思路注释,也会倾向于认为你“有思路、只是手误”,而不是“完全不会”。最后再分享一个小细节。当年批这类纸笔试卷的时候,真正让阅卷人眼前一亮的,不是答案有多精妙,而是卷面的“思路流畅感”——从解题步骤、代码缩进到注释的排版,能看出这个人平时写代码是有章法的。技术面试到后面,面试官选人往往不再对比具体的知识点,而是凭一种“这个人干事靠谱”的直觉。而笔试,尤其是手写代码的笔试,恰恰是最早建立这种直觉的地方。所以,认真对待每一次模拟,把每次练习都当成真实考场,时间不会亏待你。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进