2015年那个春天,我还在学校实验室里改简历,阿里实习生招聘的帖子在群里被转了一遍又一遍。投递系统开放的那天晚上,我蹲在宿舍把基本信息填完,心里想的是:实习生面试嘛,考察的应该不会太难。等真正坐到笔试机房,看到试卷的那一刻我才意识到,这场笔试根本不是“试试水”,它是一道真实的分水岭——考完出来,走廊里一半人脸色是白的。
现在回头看,2015年的阿里实习生笔试题,其实是那个年代互联网公司校招技术笔试的一个典型样本:覆盖的考点不会很偏,但考察密度高,混合了数据结构、算法、操作系统、网络、概率统计和一点逻辑推理,还带几道让人纠结的多选题。和现在动辄需要系统设计、源码级追问相比,当年的题目更看重“计算机基础是否扎实”和“边界条件是否敏感”。我按记忆把当时考场上印象比较深的题目、常见错误和背后的考察逻辑一条条拆出来,复盘给后来者参考。
1. 2015年那场笔试:卷子的结构、时间分配和真实难度
先说说整体情况。当年阿里的实习生笔试是在线上完成的,平台会有摄像头监控,题型大概是这几类:第一部分是行测式的逻辑推理,第二部分是技术单选和多选混合,第三部分是两道左右的手写编程题。整套卷子限时大概一个半小时到两个小时,具体时长记不太清了,但说实话,如果你在单选题上磨蹭太久,后面的编程题基本没有认真写的时间。
我印象里技术单选题大概覆盖了C/C++、Java、数据结构和操作系统,少数题目会涉及数据库和网络。多选题的杀伤力最大,因为它在计分上采用“少选不得分、错选扣分”或者“少选得部分分”的规则,考场上很多人为了求稳,只选一个自己100%确定的选项,结果把一道3分的题做成了0.5分,非常可惜。
编程题倒不算极难,但它的读题和构造边界条件比较费时间。比如有一道题和“字符串循环移位”有关,表面上考的是字符串处理,实际上考的是取模运算和原地移动。如果你平时只刷过LeetCode的简单题,没注意过这类细节,很容易写出一个测试用例过、隐藏用例全挂的代码。
如果我们把2015年阿里的笔试难度和现在对比,最大的差异在“广度”和“深度”的取舍上。当时的笔试更偏广度,考点是一个大筛子,筛掉的是基本功有短板的人;而现在的技术面试则更偏深度,一轮电话面就可能问到你项目里的某个中间件原理。如果你现在要准备阿里的实习笔试,我的建议是:不要只刷题,把CS基础课重新过一遍,把书上的概念变成自己能说清楚的逻辑。
2. 那些年让人印象深刻的算法题:从读题到AC的全过程
2.1 “字符串循环移位”不只是你会不会写代码的问题
笔试中的一道编程题大概是这样的:给定一个字符串和一个整数K,要求把字符串循环右移K位,例如"abcde"右移2位得到"deabc"。要求空间复杂度尽量低。
很多人看到这道题的第一反应是先取模(K对字符串长度取模),然后截断拼接。这在Java或Python里确实两行就能写出来。但问题在于,当年的笔试环境对语言有C/C++的倾向性,而且很多隐藏用例的K值很大,比如K=1000000007,如果你没先取模就直接开辟一个等长临时数组,一点问题没有;但如果你试图用嵌套循环一位一位搬,基本会超时。
这道题真正考察的是“三次翻转法”:先翻转整个字符串,再翻转前K个字符,最后翻转剩余部分。以"abcde"右移2位为例,先整体翻转变成"edcba",再翻转前2位"de"得到"decba",再翻转剩余部分"cba"得到"deabc",完成。
坑在于:边界条件。K可能等于字符串长度、大于字符串长度,也可能等于0。如果你只写K %= len没考虑到len为0的情况,空字符串的用例会让你直接报错。我当时就吃过这个亏,在一个看起来人畜无害的空字符串用例上扣了分。
2.2 求最大连续子数组和:经典题目里的非经典错法
另一道题是典型的“最大连续子数组和”(LeetCode 53题)。给一个整数数组,找出一个具有最大和的连续子数组,返回其最大和。
这道题大家都熟悉,标准解法是动态规划(Kadane算法),dp[i] = max(nums[i], dp[i-1] + nums[i]),同时用一个全局变量维护最大值。但笔试的陷阱不在于你不知道这个算法,而在于你对“全负数数组”的处理。
我当时旁边有位同学,很快写出了解答,核心逻辑是:如果当前累加和小于0,就重置为0。这个思路在存在正数的数组上是对的,但在全负数数组上会返回0而不是最大的那个负数。笔试里这类用例不会直接写成全负数,它可能藏在大量随机测试里。
所以正确的做法应该是在Kadane算法基础上,维护一个max_so_far的初始值为数组的第一个元素,而不是0。类似的问题还有“股票买卖最佳时机”,它本质上是最大连续子数组和的一个变体,笔试时经常把它们混着出,考察你能否识别出核心模型。
2.3 链表相关的“送分题”:环的检测和入口
有一道让我印象很深的选择题,考的是“如何判断一个链表有没有环”。选项里包含“用快慢指针”“用哈希表记录访问过的节点”“反转链表后看头节点是否和原头节点相同”等。
其实当时有不少人选了“反转链表判断环”这个方案。它的原理是:如果一个链表存在环,反转操作会陷入死循环或者导致无法正常结束。所以这个选项理论上有一定迷惑性——但在实际工程里根本不敢用,因为万一有环,程序直接卡死,没有任何保护。这提醒我们,笔试选择题的每个选项背后都有授课老师的“小心思”,它不只是考概念正不正确,而是在考你对边界场景的敏感度。
进阶一点的问题是“如何找到环的入口节点”。方法不复杂:快慢指针第一次相遇后,一个指针从头节点出发,另一个从相遇点出发,都以步长为1前进,再次相遇的位置就是环入口。笔试中不排除有这样的填空题或者编程题变形,你们考前最好自己写一遍,别只看题解。
2.4 概率统计题:考察的不是公式,而是逻辑的完备性
那套卷子里还有一道概率题,大意是两个人轮流掷硬币,先掷到正面的人获胜,问先手获胜的概率。学过概率论的同学都知道,设先手获胜概率为P,第一次掷到正面则胜,概率是1/2;如果第一次反面,那么轮到后手,此时后手获胜的概率就是P,意味着先手最终获胜的概率是(1/2)(1-P)再加第一次正面那一项:P = 1/2 + (1/2)(1-P),解得P = 2/3。
这种题在笔试里核心不是让你算这个2/3,而是看你能否快速建模。很多人容易搞混的是“无限次游戏”的处理方式,总想着展开成一个无穷级数,结果去算了1/2 + 1/8 + 1/32 + ...,也解得出来,但时间上不划算。从应试策略来说,这种题如果在选择题里出现,套公式和递归两种思路都要熟练,缺一不可。
3. 操作系统与网络的选择题里,藏着基本功的试金石
3.1 进程、线程与并发:不冷静分析就会选错
2015年的笔试里,关于进程和线程的题至少出现了两道。一道是“下列说法正确的是”,选项涉及进程和线程的地址空间、资源开销、调度单位等。线程比进程拥有更小的开销,同属一个进程的多个线程共享地址空间,而每个进程有独立的地址空间,调度上,现代操作系统一般以线程为基本调度单位。
如果把这道题直接背结论,那不太可能错。但笔试往往不会只考概念,它会给你一个具体场景,比如“在多线程程序中,两个线程同时执行i++操作10000次,最终i的值是多少”。如果你不知道i++本质上是一个“读-改-写”的三步操作,也不了解线程切换可能发生在任意一条指令之间,就会直接答10000。正确结论是最终值小于等于10000,具体多少取决于竞争情况。
这里顺便提醒一下:阿里的笔试多选题里,如果同一种技术从不同角度反复出,说明这个知识点是他们筛选人的重要信号。进程线程、并发安全、死锁的产生条件,这些在实习生面试里几乎场场出现,建议复习到位。
3.2 内存管理里的分页与虚拟内存:“缺页中断”不是玄学
有一道题问的是虚拟内存的作用,选项里有“扩大物理内存容量”“让每个进程拥有独立的地址空间”“提高CPU利用率”“硬盘空间不够时自动扩展内存”。正确的是“让每个进程拥有独立的地址空间”,以及一定程度上的扩大可用内存空间,但“扩大物理内存容量”是典型错误项,因为虚拟内存使用的是磁盘空间,物理内存并没有变大。
这个知识点的考察意在让你明白:操作系统给进程的地址空间是虚拟的,缺页中断会把磁盘中的数据调入内存,页面置换算法(LRU/FIFO/Clock)决定淘汰谁。当年笔试不要求你手写LRU,但它可能出现在多选题里,让你判断几种说法的正误。如果你在项目里用过Redis的maxmemory-policy allkeys-lru,或者处理过Elasticsearch的堆内存设置,对这些概念会更有实感。
3.3 TCP三次握手和四次挥手:不只是背状态码
网络部分的高频考点是TCP/UDP的区别、三次握手的细节、TIME_WAIT状态。
当时的题目大致是:在TCP连接建立过程中,第二次握手(SYN+ACK)的作用是什么?选项有“确认客户端的SYN并同步自己的初始序列号”“同时传输应用数据”“双方进入ESTABLISHED状态”等。正确答案是“确认客户端的SYN并同步自己的初始序列号”,但很多人会把“双方进入ESTABLISHED状态”混淆进去,因为第二次握手完成后客户端已经进入ESTABLISHED,服务端其实还要等第三次握手才进入。
为什么这些在实际工作中很重要?因为你在排查线上问题时,如果服务端出现大量TIME_WAIT状态连接,你能推测出是主动关闭连接的一方在高频创建短连接,进而考虑“长连接复用”或“调大端口范围”。阿里的笔试不直接考你线上排查,但它的出题思路就是在为后续的面试铺路:基础概念懂不懂,直接决定你能否进入下一轮。
3.4 DNS、HTTP和Cookie:这些“低级”知识反而拉开差距
有一道题问“HTTP协议中Cookie的作用”,选项涉及会话保持、用户追踪、缓存控制等。这道题本身不难,但它在2015年的试卷里却让不少人丢分,原因是它和多选题混在一起,少选了“用户追踪”这个选项。
现在做前端、后端的同学,天天和Cookie打交道,但真要你说清楚Cookie和Session的区别、Cookie的HttpOnly属性有什么意义、SameSite属性的作用,很多同学反而会卡壳。笔试里的低阶题并不代表低价值,它是对一个工程师“深度”的第一轮扫描。如果你对这类问题不熟,建议系统地过一遍HTTP的头部字段,别只停留在“会发请求”的层面。
4. 笔试中的隐藏扣分点:从代码风格到边界条件的全面复盘
4.1 读题才是第一生产力:3分钟写完的代码可能是废的
我在笔试里最大的教训是:读题太急。当时的编程题里有一句话是“如果有多个满足条件的子数组,返回起始位置最靠前的那个”,很多人在刷LeetCode时习惯了只返回最大和,忽略了题目里附加的下标要求。
所以无论你是在牛客网刷真题还是在LeetCode刷题,都要养成先做题前分析的习惯,把题目里的限制条件圈出来:输入范围、是否包含负数、是否需要返回下标、是否需要稳定排序、是否需要去重。特别是笔试现场,没有调试环境或者调试环境很弱,你更需要依赖静态阅读来发现潜在问题。
另外,很多笔试平台是单文件提交,只允许你写一个函数或者一个类,不允许你自定义工具类。这意味着你要花点时间适应“在隔离环境里裸写代码”,不要依赖IDE的自动补全。平时练习的时候,我建议打开记事本或者在线编辑器写代码,不借助任何智能提示,这样到了考场才不会手忙脚乱。
4.2 输入输出格式:菜鸟和老鸟的分水岭
当年阿里的笔试编程题用的是标准输入输出,如果你在本地用文件读取数据,那提交上去基本0分。这个问题看着很基础,但每年都有不少同学挂在上面。
C++用cin/cout,Java用Scanner/System.out.println,Python用input()/print()。如果数据量特别大(比如10^5级),Scanner可能性能不够,推荐用BufferedReader。这些细节在正常开发中无关紧要,但在在线评测里就是生死线。
我记得有年有同学在群里吐槽,说他代码逻辑完全正确,但最后超时了。后来发现他用System.out.println()在一个循环里输出上万个结果,每行一次IO,不超时才怪。正确的做法是用StringBuilder拼接再统一输出。
4.3 多选题的计分策略:少选不是最优解,乱选才是大忌
关于多选题,当时我们的共识是:先排除确定错误的选项,再对剩下不确定的选项做风险评估。
如果计分规则是“全部选对得满分,选对但不全得一半分,错选得0分”,那你完全可以采取“保底策略”:只选两个你有把握的,剩下一个不确定的宁可不选。因为再蒙一个选项,如果蒙错,你连一半分都没有;如果蒙对,你只是多了半分的增量,性价比不高。
反过来,如果计分规则是“多选、少选都不得分”,那你就更需要依靠排除法来锁定答案。有些多选题的选项设置其实很“放水”,比如四个选项里有两个互相矛盾,那么必然一真一假,你至少能排除一个。别小看这点技巧,在整套卷子里,多选题往往是区分度最高的题型。
4.4 知识点盲区:遇到不会的题如何快速止损
笔试时最忌讳的是和一道题“死磕”。我遇到不会的多选题,先标记下来,把把握大的题目全部做完再回头思考。因为前面的单选题和编程题分值高且确定性更强,你的时间应该优先投资在“高确定性、高回报”的题目上。
如果遇到完全不熟悉的知识点,比如某个冷门的命令或者某个框架的配置项,不要慌,试着用“排除法+常识推理”。2015年那套题里其实也出现过一道和Linux进程管理相关的题目,选项里是ps、top、kill等命令。这种题哪怕没有系统学过Linux,也可以用命令的英文全称去猜,ps是“process status”,top是“table of processes”,它们各自的职责范围其实是能推测出来的。
5. 考完之后:从笔试到面试,我做了什么调整
5.1 笔试暴露了知识地图上的哪几个洞
笔试结束后,我没有急着对答案,而是先把所有拿不准的题记下来,分类整理。比如发现自己对“线程安全”相关的概念模棱两可,就在面试前集中补了一遍Java并发(synchronized、volatile、Lock、ThreadLocal)。又比如发现自己对TCP的TIME_WAIT状态只知道名词、不懂原因,就去看了一些线上排障的博客文章。
这个过程比“每天刷十道题”更有效,因为它是基于真实漏洞的定向修复。笔试其实是一份免费的“体检报告”,你考得不好,不代表你能力不行,只代表你的知识结构和阿里的考查范围有重合度低的地方,查漏补缺是你唯一需要做好的事情。
5.2 项目经历在面试中的权重:笔试进面之后,故事才刚刚开始
过了笔试,会进入一轮电面和一轮现场面。阿里实习生面试里,项目经历几乎必被深挖。我当时准备了一个电商秒杀相关的课程设计项目,结果面试官问的是:你的系统并发量是多少?数据库连接池怎么配置的?如果Redis挂了,你的方案还能撑住吗?
这些问题直接把我问住了,因为我的项目只停留在“能用”的层面,没有深入考虑过性能、可用性、容错。后来我才意识到,实习生面试考察的不是你做过多大规模的项目,而是你对“系统设计”的基本感知。即使是学生项目,你也可以主动给自己加难度,比如引入缓存、消息队列、限流策略等,然后在简历上写明你考虑过这些问题。面试官看重的不是项目本身,而是你思考问题的维度。
5.3 智力题和场景题:不只是脑筋急转弯
2015年的笔试题里其实有一小部分逻辑推理题,比如“一个盒子里有红黑两种球,随机取两个,如果颜色相同就放回一个红球,否则放回一个黑球,最后剩什么颜色”这类概率/状态题。这类题看似难,其实考察的是不变量思维。笔试之后,面试中也可能出现开放式的场景题,比如“如果让你设计一个短链接系统,你会怎么设计”。
这些开放题并没有标准答案,面试官关注的是你会不会先问需求边界:每天的请求量有多大?链接有效期多久?需不需要统计点击率?这些问题的提出本身就体现了一个工程师的分析能力。所以平时训练时,不要只做输入输出确定的算法题,多尝试“从模糊需求出发设计方案”的练习。
6. 这套2015年的题,对2025年的你还有多少参考价值
很多人会问:现在都2025年了,互联网技术栈早就不一样了,2015年的实习生笔试题还有参考价值吗?我的答案是:题型会变,考点会变,但底层能力模型没有变。
现在的笔试更多集中在LeetCode中高难度的算法题,甚至部分公司开始引入系统设计题和并发编程题。你看到的变化是:字符串翻转、链表判环这类基础题变少了,动态规划、图论、DFS/BFS的考察变多了。这说明筛选标准在逐渐提高,但操作系统的进程线程管理、网络的TCP状态、数据库的事务隔离级别,这些基础题依然是笔试和面试的底色,它们不会因为你用的是Spring Boot还是Go框架而改变。
如果你现在还在准备阶段,我建议你做两件事。第一,把CS基础课的系统性复习放在刷题之前,因为基础不牢,刷题刷到一定程度会遇到瓶颈。第二,养成“从工程角度想问题”的习惯,写完一个功能之后多问自己几遍:这段代码在高并发下会怎样?这个数据结构的内存占用是多少?如果服务崩溃了,数据如何恢复?
回到2015年那场笔试,它对我来说其实是一次非常正面的挫折。它让我在正式进入职场之前,就意识到学校里的成绩和真实工程师的能力之间还有一段需要自己填平的距离。考完试的那天下午,我在图书馆把数据结构教科书重新翻了一遍,从数组、链表、栈、队列,到树、图、排序、查找,一章一章过,那是我大学几年里学习效率最高的一个下午。如果你也刚好在准备某家公司的笔试,我希望这篇复盘能让你少走一点点弯路,也让你在机房里看到一个陌生题目的时候,能多一分从容。