先说说背景。2026年3月25日,我参加了小红书的校招笔试第二套试卷。说实话,这套题整体难度比我预想的要高一些,尤其是最后一道逻辑类编程题,现场做的时候差点翻车,好在后来冷静下来把状态转移捋顺了。这套卷子的题目覆盖面比较典型:选择题部分是基础知识快问快答,涉及Java/Python基础、操作系统、计网和数据库理论;编程题部分则是三个大题,综合考了算法、数据结构以及业务抽象能力。
写这篇复盘,主要目的是帮接下来要参加小红书笔试的同学把题型分布、做题思路和坑点梳理清楚,尤其是想针对“笔试时间怎么分配”“选择题遇到不会的怎么蒙才能少丢分”“编程题卡住了从什么角度去冷静排查”这几个核心问题给出一个可落地的操作参考。如果你准备投的是后端、客户端、数据开发这类方向,这篇内容会比较对路。
1. 笔试完整时间线与整体观感
1.1 考试基本信息与试卷结构
这场笔试的总时长是120分钟,从进入考试系统到交卷,中间不能退出、系统也会全屏锁定并开启摄像头监控。整体题量属于中等偏多,我答完还有10分钟左右用于逐题检查,但检查得比较匆忙,只重点看了每道题的格式和边界情况。
题型主要分为两个大块:
- 单选题 + 多选题:一共20道左右,覆盖计算机基础、语言特性、数据库知识、少量前端概念。
- 编程题:一共3道,难度呈现明显的梯度分布,第一题偏简单,第二题中等,第三题需要一定的建模能力。
这里提醒一下,小红书用的是自己的在线评测系统,部分题目在测试用例上报错后的提示比较简略,所以自测要尽可能覆盖边界值,不能全部依赖纸面推导。
1.2 不同岗位试卷差异分析
试卷名义上叫“第二套”,说明同一批次至少有不同侧重的多套题在随机分发,用来防止邻座抄袭。我抽到这套的后端倾向比较明显,选择题里Linux、JVM和MySQL索引占了不小比重,而且多选题很多都是“选出所有正确项”,这类题比较考验对概念的完整理解,不能只记结论。
如果你应聘的岗位偏前端,大概率会遇到CSS布局、浏览器缓存机制、事件循环相关的题;如果是算法岗,选择题数量可能会更少,但编程题的题量和难度都会加大。我这边只针对自己实际参加的这套后端试卷展开复盘。
1.3 这套题整体风格的核心特点
通过做完整套题目,个人认为小红书笔试出题风格有三个明显特点:
第一,业务场景渗透很多。即使是考基础知识的选择题,题干里也经常套一个“用户在刷推荐流”“笔记搜索排序”之类的背景。不要被花哨的前缀迷惑,核心考的仍然是标准知识点。
第二,编程题不考偏题怪题。三道题没有过于刁钻的数学问题,全部是经典算法的变体,只不过披了一层业务外壳。你如果能快速抽象出本质,解法其实很直接。
第三,题型之间没有严格隔离感。意思是有些选择的知识点,比如二叉树遍历,会在编程题里继续出现,形成一定呼应。这提醒我们复习时候不要割裂地刷题,要注重知识网络。
需要模型API调用? 免费领10W Token,多模型网关一键接入 Claude、DeepSeek 等主流模型。
2. 选择题环节:考点分布与答题时间分配策略
2.1 选择题考点分布复盘
选择题总计约20道,因为回忆存在一定偏差,我这里只列出印象比较深、也较有代表性的几类,供大家判断复习重点:
- 计算机网络:TCP三次握手断开连接的状态变迁、HTTP状态码语义、DNS解析过程。
- 操作系统:进程和线程共享资源的区别、死锁产生的必要条件、虚拟内存页面置换算法。
- 数据库:索引为什么用B+树不用B树或红黑树、事务隔离级别与脏读幻读的对应关系、最左前缀匹配规则。
- Java基础:HashMap在JDK1.8中链表转红黑树的阈值、ConcurrentHashMap分段锁/同步锁机制、JVM垃圾回收中可达性分析。
- 编程语言交叉题:Python GIL的作用范围、闭包变量绑定时机、装饰器执行顺序。
从这些题目分布能看出,后端的复习重点依次是:集合源码、并发编程、JVM、MySQL索引与事务、操作系统核心概念和网络协议。这些内容如果平时有积累,20道选择题基本能控制在20分钟以内答完。
2.2 选择题的做题节奏与取舍
我的策略是:先快速过一遍所有题目,把一眼能确定答案的直接选完;剩下犹豫不决的题做标记,最后再集中判断,不允许在一道选择题上超过3分钟。
举一个典型的例子,有道题问“Linux中哪个命令可以查看文件被进程占用的情况”,备选项是lsof、ps、netstat、top。如果只是在背命令列表,可能会纠结,但如果你有实际排查问题的经验,知道lsof是list open files的缩写,再联想还能用于查看端口占用,基本可以秒选。
另外多选题是丢分重灾区,因为少选、多选、错选都不得分。我的建议是:不确定的选项不要加入选择集合,优先保确定项。这和高考多选策略不同,校招笔试的多选通常没有部分得分机制,选得越激进越吃亏。
2.3 利用选项特征辅助判断的实战技巧
这里分享一个我在现场用到的、不算作弊但很有效的技巧:选项里的绝对化表述往往是错误的。比如某个选项说“使用索引一定能提升查询性能”,这种说法缺少前提,因为选择性低的列使用索引反而可能更慢。
反过来,表述中带着“通常”“在一定条件下”“可能”这类限定的选项,正确率往往偏高。这不是玄学,是因为出题人需要在严谨表述下保证选项的科学性,而正确选项更倾向于严谨表述。
如果遇到完全不会的底层源码细节题,比如“ConcurrentHashMap扩容时,其他线程如何协助迁移数据”,先回忆相关源码阅读笔记,如果实在回忆不起来,再看题目有没有给出“协助迁移是指从原数组搬移到新数组的哪个方向”这种限定,能大大缩小猜测范围。
3. 编程题拆解:从读题到AC的全过程实录
3.1 第一题:数组频次统计与按规则排序
题目大意:
给定一个长度为n的整数数组,请按照元素出现频次从高到低排序;如果频次相同,则按照元素数值从小到大排序。要求输出排序后的完整数组,n最大为10^5,元素绝对值不超过10^9;要求时间复杂度不超过O(n log n)。
思路分析:
这题本质是“根据频次排序”,我们需要对原始数组每个元素做二次映射。最常见做法是先用哈希表统计频次,再对数组进行一次自定义排序,比较器先比频次降序,再比数值升序。
java复制public int[] frequencySort(int[] nums) {
Map<Integer, Integer> count = new HashMap<>();
for (int num : nums) {
count.put(num, count.getOrDefault(num, 0) + 1);
}
Integer[] boxed = Arrays.stream(nums).boxed().toArray(Integer[]::new);
Arrays.sort(boxed, (a, b) -> {
int freqA = count.get(a);
int freqB = count.get(b);
if (freqA != freqB) {
return freqB - freqA;
}
return a - b;
});
return Arrays.stream(boxed).mapToInt(Integer::intValue).toArray();
}
现场遇到的问题:
第一遍我直接对原始数组排序后重建,结果频次在排序过程中没有同步更新导致结果错乱。后面调整为先统计再排序,逻辑就清晰了很多。还有一个很容易被忽略的隐藏条件是:数值可能有负数。比较器里如果用a - b的方式在极端情况下可能出现溢出,虽然测试用例未必覆盖,但严谨起见建议用Integer.compare写。
3.2 第二题:环形区域内获取能量最大值
题目大意:
有n个点围成一个环,每个点上有一个非负整数能量值。从一个点出发,可以沿着环顺时针走,每到一个点获取该点能量,但一旦获取第k个点时路径必须停止,并且不能重复获取同一个点的能量。要求计算能够获取到的最大能量和。这些点的数据规模是n≤10^5。
思路分析:
如果n达到10^5但k并没有远小于n,这题考查的核心其实是“如何处理环形序列的遍历问题”。一个标准套路是把原数组复制一遍,长度变为2n,这样任意长度不超过n的子段都能在展开后的线性数组中找到连续表示。
不过这题有个隐含细节:如果允许跨越起点多次循环,那么我们需要结合前缀和的最小值来维护滑动窗口。在现场我记得我把问题简化成:求所有长度为k的环形窗口中,区间和的最大值。
java复制public long maxEnergy(int[] energy, int k) {
int n = energy.length;
long[] prefix = new long[2 * n + 1];
for (int i = 0; i < 2 * n; i++) {
prefix[i + 1] = prefix[i] + energy[i % n];
}
long ans = 0;
Deque<Integer> deque = new ArrayDeque<>();
for (int i = 0; i < prefix.length; i++) {
while (!deque.isEmpty() && deque.peekFirst() < i - k) {
deque.pollFirst();
}
if (!deque.isEmpty()) {
ans = Math.max(ans, prefix[i] - prefix[deque.peekFirst()]);
}
while (!deque.isEmpty() && prefix[deque.peekLast()] >= prefix[i]) {
deque.pollLast();
}
deque.addLast(i);
}
return ans;
}
关键点复盘:
这题比较容易卡壳的地方是为什么需要维护前缀和的单调递增队列。简单说,我们固定窗口的右端点,想要窗口内的和最大,那在窗口允许的起点范围内,前缀和越小,相减得到的区间和越大。因此需要一个能够快速获取窗口内最小前缀和的数据结构,单调队列正好满足。
同时要注意,这里的k如果小于n,环的复制只需一遍即可,而如果k可能大于n,则要根据“不能重复获取同一个点”直接过滤掉非法情况。我在答卷中并没有处理k大于n的情况,因为题目已经说明停止条件,仔细审题后确认该条件限制了窗口长度不会超过n。
3.3 第三题:状态机动态规划与业务建模
题目大意:
有一笔订单,在n个连续时间点内有多次操作,操作分三类:创建订单、支付订单、取消订单。要求统计所有可能的有效订单状态转移路径数量。规则限制:订单必须先创建后支付,已经支付不能取消,取消后不能支付。最终n个时间点结束后,只保留状态为已支付或已取消的订单。数据范围n最大2000,结果对10^9+7取模。
思路分析:
这是一道披着业务壳的状态机DP。我们为每个时间点维护三个状态的数量:
- 状态0:订单还没有创建。
- 状态1:订单已创建但未支付。
- 状态2:订单已支付(终态)。
- 状态3:订单已取消(终态)。
其中状态2和3都属于结束后仍然有效的订单,但两者之间不能互相转化。用一个二维数组dp[i][state] 表示第i个操作结束时状态为state的路径数量,每次操作按类型转移即可。
java复制public int countValidPaths(String[] operations, int n) {
long[][] dp = new long[n + 1][4];
dp[0][0] = 1;
long mod = 1000000007L;
for (int i = 1; i <= n; i++) {
String op = operations[i - 1];
// 创建操作
if (op.equals("create")) {
dp[i][1] = (dp[i][1] + dp[i - 1][0]) % mod;
dp[i][0] = (dp[i][0] + dp[i - 1][0]) % mod;
} else if (op.equals("pay")) {
dp[i][2] = (dp[i][2] + dp[i - 1][1]) % mod;
dp[i][0] = (dp[i][0] + dp[i - 1][1] + dp[i - 1][2] + dp[i - 1][3]) % mod;
}
// 取消操作
else {
dp[i][3] = (dp[i][3] + dp[i - 1][1]) % mod;
dp[i][0] = (dp[i][0] + dp[i - 1][0]) % mod;
}
// 其他维持原状态不动的转移需要根据题意处理
}
long ans = (dp[n][2] + dp[n][3]) % mod;
return (int) ans;
}
这里需要说明,以上代码是我复盘后整理出的核心循环,不是考场完整代码,因为考场上的状态转移实际上比重构版更多,还涉及“多次操作之间未发生动作时状态保持”的路径数累计。如果不把维持状态的部分加上,答案会严重偏小。
考场上的卡点与化解过程:
一开始我把问题简单理解成一个事件只能发生一次,导致样例输出不匹配。反复推导了大概10分钟后,我意识到同一个时间点上可能有多个分支路径累计,必须为每个状态加上“保持不变”的转移项。这是DP初始化之外最常见的一个坑,也是我这场笔试里丢分风险最高的一处。
回头再看,这种状态机DP如果提前做过几个经典场景题(比如股票买卖含冷冻期、用户留存状态流转),是不会感到慌乱的。第三题本质上使用了完全相同的建模思路:抓住核心状态集合,穷举每步可到达状态,其余交给循环转移。
3.4 编程题整体时间分配复盘
三道题我实际花费时间大约是:第一题15分钟,第二题35分钟,第三题45分钟,剩下25分钟在做检查。从时间使用来看,第一题性价比最高,编程能力中等的同学应该尽可能一次通过,不要在这题上翻车。
第二题的单调队列思路其实不难,但我在滑动窗口左边界收缩的时候写错了一个索引细节,花了5分钟才定位出来。如果大家平时刷题习惯用IDE的debug功能,笔试环境里没有断点调试,需要在关键位置手动打印中间变量来定位问题,速度会慢一些。
对于第三题,如果你是按照先写状态转移再补边界条件的顺序来写,会比较顺手。更推荐先把状态定义和转移方程完整写在草稿纸上,再进行编码。这也是我个人复盘后建议所有人的一个习惯转变。
4. 笔试环境的硬性规则与上手准备
4.1 在线考试系统使用注意事项
小红书的笔试会提前发短信和邮件通知,邮件里附有考试链接和账号信息。建议至少提前30分钟进入系统,因为往往包含人脸识别、屏幕录制授权和环境检测等步骤,第一次操作会比较花时间。
进来后会有一个简短的页面说明,里面会写明试卷总题量、答题时间以及每部分是否允许返回修改。以这套试卷为例,选择题做完了可以回看,但编程题提交后是否还能修改要看具体场次安排,保险起见最好先在本地把所有代码调试完毕再粘贴进系统。
4.2 本地方言:编程环境的踩坑经历
这个坑在牛客网也有同样情况。很多在线评测系统默认选的是Java 8,但本地项目的JDK版本可能是17,有些语法需要调整。
另一个我踩过的是本地IDE编译通过但系统编译报错,原因是系统对类名要求是Main。虽然看起来是一个不起眼的点,但如果考试前没有准备好编译模板,很容易在紧张状态下反复提交报错,既浪费时间也影响心态。
我的建议是提前把所有常见的IO模板准备好,按自己熟悉的语言准备好一份,比如:
java复制import java.util.*;
public class Main {
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
int n = in.nextInt();
int[] arr = new int[n];
for (int i = 0; i < n; i++) {
arr[i] = in.nextInt();
}
// 业务逻辑
System.out.println(ans);
}
}
4.3 摄像头与浏览器兼容性问题
在线笔试系统一般限制只能使用Chrome或Edge浏览器,并且会要求关闭所有远程控制软件、录屏软件。我自己使用的是Chrome 120版本,整个过程没有遇到兼容问题。
建议考试之前检查一下网络是否稳定,因为如果中途断网超过一定时间,系统可能会强制交卷。这个问题不可控,但可以在开考前用移动热点做备用网络策略,一旦主网络出现波动能快速切换。
4.4 草稿纸与计算器准备
虽然笔试题目以代码为主,但选择题里的网络计算、操作系统页面置换等有时候需要画表推理,准备好几张空白A4纸和一支笔很关键。
另外,系统内可能自带一个简易计算器,但位置隐蔽且不支持复杂运算。遇到涉及二分查找比较次数、哈希表扩容时的地址重映射这类计算时,用纸笔推演其实比按计算器更快。
5. 笔试复盘方法论:如何把一场考试价值最大化
5.1 做题过程中的信号记录法
考试进行中如果遇到不确定的题目,我会在草稿纸上记录下来题号和卡壳的关键点。不只是为了当场检查,更重要的是考后复盘时能精准定位薄弱环节。
比如本场考试我在草稿纸上记了这样几条:
- 多选5:不确定synchronized和volatile在可见性上的区别。
- 编程题2:单调队列的初始化push时机,差点越界。
- 编程题3:创建和支付操作之间很多分支漏加保持不变路径。
考后对照这几个信号去复习,效率比漫无目的地刷一套新题高很多。信号本身暴露的就是你在真实时间压力下的知识盲区。
5.2 考后逐题代码重写的最佳实践
笔试结束后,我建议尽量在当天把三道编程题重新写一遍,不借助任何外部提示,目标是独立通过全部测试用例。隔天再重写一次,看是否能够一次通过。
这种方法能检验你是真的掌握了这类题型,还是只是现场急中生智凑出了一个能过样例的解法。像第三题状态机DP,我在当天重写时仍然需要翻看转移方程,但隔天重写的时候就已经非常顺畅,这代表知识初步固化到长期记忆里。
5.3 把错题按知识模块归类整理
错题整理不应该只是把标准答案抄一遍,而是要把考点映射到对应模块。比如“TCP三次握手状态迁移”和“HTTP状态码含义”虽然都是选择题,但一个属于网络协议的状态机,一个属于应用层语义,需要分别归入不同复习卡片。
我在复盘本场试卷时,整理出的薄弱模块是:单调队列使用场景、动态规划的状态转移细节、JVM垃圾收集器中对象存活判定算法。这三个方向也是接下来一个月专项刷题的重点。
5.4 建立自己的笔试冲刺题库
最后想强调的是,不要在临近笔试时才疯狂刷新题,而是应该建立一份自己的冲刺高频题库。里面收集的题应当来自过往大厂真题的高频考点,比如线段树与树状数组、状态机DP、拓扑排序、字典树、KMP等。
小红书这套题里面第三题的风格比较接近牛客网上“游戏订单状态流转”类模拟题,如果你在题库里练过类似的业务建模题,考场上至少能节省10分钟用来推演边界。
6. 整套试卷的避坑指南与提分思考
选择题部分最容易丢分的不是纯背诵题,而是带有二分查找边界、哈希冲突链地址法平均查找长度计算这类需要动手推算的小计算题。平时只刷概念题的习惯需要调整,建议每天抽20分钟专门练这类计算推导,考试时就能快速作答。
编程题部分,第一题提醒我们Java的Comparator底层用的是TimSort,如果比较器不满足传递性会抛出IllegalArgumentException,但如果你使用Integer.compare可以天然避免这个问题。第二题提醒我们环形数组的常用处理是复制拼接,但核心是前缀和配合单调队列;第三题提醒我们,状态机DP的转移不仅要考虑触发事件的情况,还要考虑事件未触发时状态沿时间线自然顺延的情况。
考场上的心态管理也同样重要。我给自己定的一个纪律是:如果一道编程题在25分钟内没有完整思路,就先把暴力解法写出来,确保有一定的基础分,再回来想优化。本场第二题如果我先写暴力版本,也许能在更短时间内发现单调队列的性质,而不是卡在窗口左边界缩放的细节上纠结。第三题因为涉及的复杂度过高,反而更依赖草稿纸上的推导,好在我没有急着敲代码,才避免了重复返工。
关于这套题想说的基本就这些,接下来趁热打铁,我会把第三题的状态机转移矩阵重新手写一遍,并额外用滚动数组方式压缩空间,相当于再做一轮巩固。如果后续收到面试通知,我还会在准备过程中继续更新相关考题的复盘内容。
