
刚做完蚂蚁这轮开发岗的笔试趁热打铁把手写的思路和踩的坑整理出来。这场是 2026.03.15 的场次岗位方向是后端开发整体题型、难度、时间安排都还算典型准备投大厂开发岗的朋友建议认真看一眼尤其是里面那几道题目的思考路径比自己闷头刷题要有价值得多。这次笔试一共 120 分钟题量大头是算法编程题外加少量的基础选择题。选择题部分考得比较杂有 Java 基础、数据库索引、操作系统进程调度还有一两道网络协议相关的概念题整体难度中等不偏不怪属于“认真复习了就有分”的类型。编程题部分才是真正拉开差距的地方一共四道前三道是标准输入输出的 ACM 模式最后一道偏工程场景建模考得挺活。先说一下整体感受蚂蚁的开发岗笔试风格和纯竞赛题不太一样它更看重“能不能把一个实际问题抽象成算法模型”然后要求代码在边界条件下依然稳得住。所以平时刷题只追求“能跑通样例”远远不够边界条件和复杂度分析才是拿分关键。1. 笔试整体结构与时间分配1.1 试卷构成和分值分布先说大框架。整张卷子总分 100 分客观题占了 30 分编程题 70 分。我个人的做题顺序是先花 20 到 25 分钟解决选择题剩下时间全部砸在编程题上。不要在一道选择题上卡太久比如那两道关于数据库索引和 Java 并发包的题一眼看不出来就凭积累先选后面如果有时间再回来推敲。编程题的分值分布大概是 10 分、15 分、20 分、25 分这样往上走的。越往后越难分值也越高。很多人一上来就死磕最后一道大题结果前面简单的题没时间写白白丢分。我的策略是先快速扫一遍四道题判断一下难易梯度然后从最简单的开始写保证基础分先落袋。还有一点要提醒蚂蚁笔试用的在线编辑器虽然支持主流语言但补全和提示都比较弱基本就是记事本水平。平时用惯了 IDEA 或者 VS Code 自动补全的人上了考场写代码速度会明显下降。建议提前两三周用牛客网那种在线编辑器练手感尤其是快排、滑动窗口、堆这些常用模板要能不看资料直接默写出来。1.2 核心考察方向解析从“开发岗”这个定位出发这次笔试题考察的方向挺清晰的。选择题覆盖了计算机基础编程题则明显偏向“数据结构与算法在实际场景中的应用”四道题分别涉及字符串处理、图论建模、动态规划、以及一个类似任务调度的工程场景问题。总结下来蚂蚁开发岗笔试的核心考察点有三个第一是编码基本功包括输入输出处理、边界条件判断、常用容器操作第二是算法思维尤其是如何把业务问题抽象成经典模型第三是复杂度意识同样的题目暴力解能过一部分数据但想要全部 AC必须得拿出 O(n) 或 O(n log n) 级别的解法。我备考的时候把 LeetCode 题库按企业标签刷过一轮但真正考试的时候发现蚂蚁的题比 LeetCode 上标注为“中等”的题要稍微活一点。它不会直接告诉你“这是并查集”而是给你一个看似复杂的故事背景让你自己去发现底层模型。这一点在第三题和第四题表现得特别明显。2. 选择题里的高频考点回顾2.1 基础概念题避坑指南选择题部分我印象比较深的有几道。一道是关于 Java 的HashMap在并发场景下的问题问的是 JDK 1.7 和 1.8 在底层实现上的区别以及为什么会产生死循环。如果你只是背过八股文知道“1.7 头插法、1.8 尾插法”这道题能答上一半但题目还追问了扩容时链表分裂的条件这就得真正理解 resize 的过程才能做对。还有一道 SQL 索引的选择题给了一个员工表然后问哪条查询语句可以用到联合索引department_id, status, created_at。这种题其实就是在考最左前缀原则但陷阱在于它把条件里的顺序打乱了比如WHERE status 1 AND department_id 100很多人一看联合索引就不假思索地选“不能命中”但实际上数据库优化器会做条件重排这种场景下依然能走索引。操作系统那边考了一道关于进程调度算法的题问的是在时间片轮转调度下一个进程的平均等待时间怎么算。这道题倒是规规矩矩只要画得出甘特图就能拿分但计算量稍大建议在草稿纸上按时间轴慢慢推进别心算容易错。2.2 这些知识点为什么值得反复看很多人觉得选择题分少不值得花大量时间复习。但我的体会是选择题是最容易拿分的部分而且复习选择题的过程本身就是在帮面试做准备。比如 HashMap 的并发问题、索引失效的场景、TCP 四次挥手的状态变化这些都是面试官喜欢追问的点笔试之前过一遍相当于一鱼两吃。我当时复习选择题是拿“八股文清单”过的每看到一个知识点就问自己三个问题它是什么它解决了什么问题它在什么场景下会出问题能答上来就过答不上来就记到错题本里。这个习惯帮我省了很多时间也让我在笔试选择题部分做得比较顺。3. 第一道编程题字符串相邻去重变体3.1 题目大意与思路推导第一题大概是这么个意思给定一个只包含小写字母的字符串要求不断删除相邻且相同的一对字符直到无法继续删除为止输出最终字符串。这道题其实就是括号匹配的变体本质是栈的应用。题目给了 10 分属于送分题但送分不代表能丢分因为它的坑在于删除之后新相邻的字符如果相同还要继续删除。换句话说如果字符串是abba你如果只做一遍扫描删除bb之后得到aa这还没完必须继续删除aa最后得到空字符串。所以关键不是“找到所有相邻重复对然后一次性删掉”而是“用栈按顺序处理每进一个字符就和栈顶比较”。我第一次做的时候用的就是一次性删的思路样例过了但提交只过了一半测试点后来才反应过来问题出在“连锁反应”上。这也是笔试里最常见的思维陷阱——只顾着处理当前这一层忘了操作之后可能产生新的状态。3.2 高效实现与复杂度分析正确的解法就是用栈。遍历字符串的每个字符如果栈不为空且栈顶字符等于当前字符就弹出栈顶否则把当前字符压入栈。等到字符串遍历完栈里剩下的字符就是最终结果从栈底到栈顶的顺序输出即可。import java.util.*; public class Main { public static void main(String[] args) { Scanner sc new Scanner(System.in); String s sc.nextLine(); DequeCharacter stack new ArrayDeque(); for (char c : s.toCharArray()) { if (!stack.isEmpty() stack.peek() c) { stack.pop(); } else { stack.push(c); } } StringBuilder sb new StringBuilder(); while (!stack.isEmpty()) { sb.append(stack.pollLast()); } System.out.println(sb.toString()); } }这个解法的时间复杂度是 O(n)每个字符最多入栈一次、出栈一次。空间复杂度最坏情况下是 O(n)比如所有字符都不相同的情况。这个题想要满分还有一个细节题目如果用的是 BufferedReader 而不是 Scanner在数据量大到 10^6 级别时能省下不少时间建议提前准备一个快读模板。4. 第二道编程题带权图上的最短路径变种4.1 题目还原与建模过程第二题给了一个无向图节点和边都带权要求从起点到终点的最短路径。这本来是最短路裸题但它加了一个条件你可以把最多 k 条边的权值变为 0。考场上我一眼看过去就知道这是分层图最短路因为“把 k 条边变成 0”这个操作本质上就是在原本的图上多开 k 层每一层代表已经使用了 j 次特权。建模方式不复杂把原图复制 k 层原来的每个节点变成 k1 层里的不同状态。每对相邻节点之间除了在同一层里的正常连边之外还要从第 j 层的节点连一条权重为 0 的边到第 j1 层的对应节点。这样跑一遍 Dijkstra答案就是最后一层终点的最短距离。说实话这道题模型不算难15 分很合理考的是有没有见过分层图这个套路。如果你之前刷过“飞行路线”或者“免费搭车”这类题应该十分钟之内能建立正确的模型。但我在考场上还是犹豫了一下因为题面里没有直接说“最多使用 k 次免费机会”而是用一个叙述性的场景把条件包起来了差点没看出来是分层图。4.2 Dijkstra 的扩展与代码实现要点我实现的版本用了一个dist[i][j]的二维数组i表示节点编号j表示已经使用的免费次数。优先队列里存的是三层信息当前距离、当前节点、已用免费次数。每次从堆里弹出当前距离最小的状态然后分别尝试“花权重走普通边”和“免费走一条边”两个方向。import java.util.*; public class Main { static class Edge { int to, w; Edge(int to, int w) { this.to to; this.w w; } } static class State implements ComparableState { int dist, node, used; State(int dist, int node, int used) { this.dist dist; this.node node; this.used used; } public int compareTo(State other) { return Integer.compare(this.dist, other.dist); } } public static void main(String[] args) { Scanner sc new Scanner(System.in); int n sc.nextInt(); int m sc.nextInt(); int k sc.nextInt(); ListListEdge graph new ArrayList(); for (int i 0; i n; i) { graph.add(new ArrayList()); } for (int i 0; i m; i) { int u sc.nextInt(); int v sc.nextInt(); int w sc.nextInt(); graph.get(u).add(new Edge(v, w)); graph.get(v).add(new Edge(u, w)); } int start sc.nextInt(); int end sc.nextInt(); int[][] dist new int[n 1][k 1]; for (int[] row : dist) { Arrays.fill(row, Integer.MAX_VALUE); } PriorityQueueState pq new PriorityQueue(); dist[start][0] 0; pq.offer(new State(0, start, 0)); while (!pq.isEmpty()) { State cur pq.poll(); if (cur.dist ! dist[cur.node][cur.used]) continue; for (Edge e : graph.get(cur.node)) { int nd cur.dist e.w; if (nd dist[e.to][cur.used]) { dist[e.to][cur.used] nd; pq.offer(new State(nd, e.to, cur.used)); } if (cur.used k cur.dist dist[e.to][cur.used 1]) { dist[e.to][cur.used 1] cur.dist; pq.offer(new State(cur.dist, e.to, cur.used 1)); } } } int ans Integer.MAX_VALUE; for (int i 0; i k; i) { ans Math.min(ans, dist[end][i]); } System.out.println(ans); } }有一个细节要说明当dist[cur.node][cur.used]和堆里取出的cur.dist不一致时说明这个状态已经被更优的路径更新过直接跳过这就是 Dijkstra 的“懒删除”策略。这个优化很关键不然堆里会积压大量无效状态导致 TLE。4.3 复杂度评估与数据范围陷阱分层图 Dijkstra 的时间复杂度是 O(k(n m) log(kn))对于 n 和 m 在 10^5 级别、k 在 10 以内的情况是完全能跑的。但这里有个大坑如果你把分层图显式地建出来节点数会变成 n 乘以 (k1)边数也会跟着膨胀一倍多内存很容易爆掉。所以在实际实现里我采用的是“逻辑分层”而不是“物理分层”——不真的去建一份新图而是用二维状态数组来表达层数。这样做图本身还是只有 n 个节点、m 条边节省了内存也减少了初始化开销。考场上如果你看到这种题第一反应应该是“分层图 最短路”但动手前一定想清楚是显式建图还是用状态维度隐式表达。5. 第三道编程题动态规划与状态压缩的配合5.1 完全背包问题还是多重背包第三题是一个背包问题但包装得很生活化。大意是你有若干种面额不同的代金券每种代金券有数量限制问凑出某个目标金额共有多少种组合方式。这道题字面看是多重背包求方案数但数据范围给了一个关键提示——代金券的种数很少最多只有 5 种而目标金额最大到 10^5。这里就得想一下了如果真按多重背包来写把每种代金券拆成 01 背包复杂度会变成 O(n * 金额 * 数量)大概率过不了全部数据。但种数这么少意味着可以走生成函数或者状态压缩的思路甚至可以用母函数来理解每种代金券的选择情况对应一个多项式把所有多项式卷起来目标金额对应的系数就是答案。真正做题的时候我选择了把多重背包转成 01 背包时用二进制拆分优化这个方案稳妥且通用写起来也不容易出错。二进制拆分的原理很简单如果某个面额有 x 张把它拆成若干个 2 的幂次组就能用这些组表示 0 到 x 之间的任意数量而不需要一一张展开。5.2 防溢出的经典处理手法背包问题求方案数第一反应是用二维 DPdp[i][j]表示前 i 种代金券凑出金额 j 的方案数。但实际写的时候可以直接滚成一维数组只要注意内层循环要倒序遍历避免同一轮里重复使用已经更新的状态。这是 01 背包的常识但多重背包经过二进制拆分之后每一组都是一个标准 01 物品所以逻辑上完全一致。还有一个容易坑人的地方是取模。题目里给的模数一般是 10^9 加 7 这种常用大质数但我在笔试时见过有人因为忘记在dp[j] dp[j - w]处取模结果中间过程溢出导致答案全错的案例。也有人在输出答案时取模但在 DP 更新时没取等到数值膨胀到超过 long 范围才意识到问题。我的建议是每做一次加法就取一次模虽然多几行代码但能避免最恶心的调试过程。另外这道题还给了一个小陷阱目标金额可能是 0。如果是 0那答案应该是 1因为“什么都不选”也算一种方案但忘了初始化dp[0] 1的话这里就会直接错。笔试场上这种边界条件没人提醒你只能靠平时养成的习惯来兜底。6. 第四道编程题任务调度的工程建模题6.1 场景抽象与拓扑排序识别第四题是整场笔试里最有意思的一道。题目描述了一个类似任务编排系统的场景有 n 个任务任务之间存在依赖关系每个任务有预计耗时有些任务需要先等前置任务完成才能开始有些任务可以并行执行。要求计算所有任务完成的最短时间。这类题一旦看到“依赖关系”“前置条件”“并行执行”这些字眼基本就是在考拓扑排序。最短完成时间实际上就是关键路径问题只不过要在拓扑排序的同时更新每个任务的最早开始时间。具体做法是按照拓扑序遍历每个任务当一个任务结束后用它的完成时间去松弛它的所有后继任务。我当时读完题先在草稿纸上画了一个 DAG确定关系表达清楚之后才开始写代码。这个“先画图再写码”的习惯在考场上帮我省了很多调试时间。你只要把任务之间的依赖关系整理成邻接表再统计每个任务的入度就能开始拓扑排序了。6.2 拓扑排序 关键路径的完整实现实现上我用了一个队列来维护当前所有入度为 0 的任务。初始时把所有没有前置依赖的任务放进去它们的最早开始时间就是 0。然后每次从队列里取出一个任务更新它的完成时间再把它所有后继任务的入度减一如果某个后继任务的入度变成 0就把它放入队列。在这个过程中还要维护每个任务的最早开始时间一个任务的最早开始时间等于它的所有前置任务完成时间的最大值。因为要等所有前置任务都完成了这个任务才能开始。而最终答案就是所有任务完成时间的最大值。import java.util.*; public class Main { public static void main(String[] args) { Scanner sc new Scanner(System.in); int n sc.nextInt(); int m sc.nextInt(); int[] cost new int[n]; for (int i 0; i n; i) { cost[i] sc.nextInt(); } ListListInteger graph new ArrayList(); for (int i 0; i n; i) { graph.add(new ArrayList()); } int[] indegree new int[n]; for (int i 0; i m; i) { int a sc.nextInt(); int b sc.nextInt(); graph.get(a).add(b); indegree[b]; } int[] earliest new int[n]; int[] finish new int[n]; QueueInteger queue new LinkedList(); for (int i 0; i n; i) { if (indegree[i] 0) { queue.offer(i); } } int visited 0; int answer 0; while (!queue.isEmpty()) { int task queue.poll(); visited; finish[task] earliest[task] cost[task]; answer Math.max(answer, finish[task]); for (int next : graph.get(task)) { earliest[next] Math.max(earliest[next], finish[task]); indegree[next]--; if (indegree[next] 0) { queue.offer(next); } } } if (visited ! n) { System.out.println(-1); } else { System.out.println(answer); } } }注意这里有一个环检测的妙用如果把所有能处理的节点都处理完之后visited的数量不等于 n说明图里存在环。存在环就说明任务依赖关系有死锁题目里如果要求这种情况输出 -1你的代码就能准确接住。这道题在样例里就放了一个有环的例子目的就是看你能不能想到这一层。7. 编程题解题顺序与时间管理策略7.1 先通用技巧还是先冲难题编程题的做题顺序确实值得专门讲一下。我自己习惯是“先通读再排序先保底再冲刺”。拿到试卷的第一时间我会花两三分钟把四道题都浏览一遍给每道题打一个难度标签。这一轮浏览很重要它能让你避免陷入“第一题好难后面其实是水题”的窘境。具体到这场笔试第一题的栈应用属于入门难度第三题的背包属于中等难度第二题的分层图属于“看到过就会、没看到就不会”的套路题第四题的拓扑排序属于“思路不难但实现细节多”的工程题。我的做题顺序是 1 - 3 - 2 - 4把最有把握的先写掉把最花时间的放到最后。总分 70 分的编程题里拿到 50 分以上的关键在于“不纠结完美解法”。比如第二题如果你没看出分层图可以用暴力 BFS 配上剪枝拿部分分第四题如果有环不会输出 -1把拓扑排序的部分写对也能拿不少分。笔试又不是 ACM 现场赛部分分积累起来完全能够进入面试轮。7.2 时间不足时如何合理放弃这里想聊聊“放弃”这个话题。很多人在笔试时有一个误区觉得每道题都必须做出来否则就是失败。但实际上一场大厂笔试能全 AC 四道题的人是极少数大多数进入面试的人也就是做出来两题半、三题的水平。你真正要保证的是会做的题不丢分而不是把不会做的题硬啃出来。我的策略是一旦在某道题上卡了 25 分钟还没有清晰的思路就先停下来转去做下一道。因为笔试的时长是固定的多卡一分钟后面会做的题就少一分钟。这种取舍看起来很亏但实际上是效率最高的做法。等把能拿的分都拿完之后再回头啃一开始卡住的那道题心态和思路都会清爽很多。8. 考后总结蚂蚁开发岗笔试的备考方向8.1 数据结构和算法的复习优先级结合这次笔试的题目分布我来给准备蚂蚁开发岗的朋友一个复习方向参考。优先级最高的肯定是各大厂笔试的“标配四件套”栈与队列、二叉树与图论、动态规划、细节实现能力。其中图论相关的东西尤其要重视分层图、拓扑排序、并查集、最小生成树这些内容在蚂蚁的笔试题里出现频率相当高。站在我的角度这背后的逻辑也好理解。蚂蚁的核心业务高度依赖分布式系统和任务调度所以笔试题里天然会带一些“工程抽象”的味道。你以为是在做算法题实际上它是在通过算法题考察你建模的能力——能不能把一个混乱的现实场景理出清晰的逻辑结构。这就是为什么裸刷 LeetCode 不一定够用还得有意识地把算法模型往业务场景上套。8.2 输入输出、编程环境与模拟练习关于笔试环境我再多啰嗦两句。蚂蚁笔试用的在线编辑器不会像本地 IDE 那样帮你自动导包也不会提示你语法错误。平时用惯了内补全的人建议提前花一周时间适应。我个人的办法是在牛客网找几家大厂的往年真题来刷并且只用网页编辑器写不切到本地 IDE。写的时候也要注意输入输出的处理方式。Scanner在数据量小的时候没有问题但如果数据量到了 10^6 级别Scanner会比BufferedReader慢好几倍。建议不管题目数据范围写没写都直接上快速 IO 模板这个习惯关键时刻能救命。经常有朋友问我刷题刷到什么程度才敢去投蚂蚁。说实话没有绝对的量化标准但你可以自测一下给你一道 LeetCode 中等难度的题目你能不能在三十分钟内给出正确思路并且写出无语法错误的代码。如果能稳定做到笔试基本就有戏如果不能就继续把基础数据结构刷扎实不要急着投简历。这场笔试整体给我的感觉是题目不偏、不怪、不超纲每道题背后都对应着一个经典算法模型但都需要一点“场景翻译”的能力。希望这篇复盘能帮到你也希望正在准备大厂笔试的朋友都能拿到满意的成绩单。