春招编程笔试复盘:OTA平台算法题、Java并发与笔试避坑指南
2026/9/1 14:16:11 网站建设 项目流程

2023年春招投去哪儿的时候,我没想到编程笔试会刷掉那么多人。当时我报的是后端研发岗,线上笔试一共90分钟,前面20道选择题,后面4道编程题,全程在一个不支持本地IDE、只能在线提交的网页编辑器里完成。考完之后我复盘了很久,发现这类在线旅游平台的编程笔试和一般互联网大厂有些区别,不是单纯把LeetCode刷完就能拿下的。这篇文章想把当时的题型、解题思路、踩坑点都整理出来,给后面准备春招、秋招的同学做个参考,里面有些细节和教训,是我实际考试之后才真正想明白的。

1. 一次春招编程笔试,到底在考什么

1.1 去哪儿笔试的整体印象与考察定位

2023年春招是恢复线下实习和校园招聘节奏比较明显的一年,去哪儿作为在线旅游平台,技术岗笔试延续了“选择题+编程题”的混合模式。我记得笔试开始前,系统会先让你签一份在线诚信承诺,然后自动打开摄像头监控,网页不能随意切换、不能复制粘贴。这意味着你平时熟悉的本地IDE自动补全、代码片段直接拖拽这些操作,在考场上全部失效,所以准备阶段一定要养成手写代码和直接在网页编辑器里敲代码的习惯。

从题目构成来看,选择题覆盖了数据结构、操作系统、网络、数据库和Java/Python语言基础,编程题则集中在数组、字符串、动态规划、贪心和模拟这几类。去哪儿这类业务型公司,很少出特别偏门、特别竞赛向的题,更多是让你在限定时间内用代码解决一个业务里可能出现的实际问题。换句话说,它考的不是你会不会炫技,而是你能不能稳定、清晰地把一个需求写成能跑的代码。

我印象比较深的还有一点:整个笔试界面会实时显示剩余时间,而且编程题的通过率是按测试用例算的,不是只看最终有没有跑通。所以哪怕你不能AC所有用例,只要把暴力解或者部分正确的写法提交上去,也能拿到一部分分数。这个规则很关键,直接决定了我后面做题时的策略选择。

1.2 选择题里容易被忽视的知识点

选择题是我翻车比较严重的地方,因为平时刷题只盯着算法,到考场上才发现,很多计算机基础已经忘得差不多。这里多说一句,准备春招笔试时,不要把“编程”窄化成“算法”,基础知识的优先级一样高,甚至更高。

我印象比较深的一道选择题是关于数据库索引的。题目给了一张订单表,字段有用户ID、订单状态、下单时间,问下面哪个SQL语句能用上联合索引。去哪儿这类OTA平台,订单查询是最核心的业务场景,这个题几乎年年出现。答案的核心是“最左前缀原则”,也就是联合索引里字段的顺序要和查询条件从左到右匹配。如果你只建了(user_id, status, order_time)的联合索引,那么where里只写status或只写order_time,都用不上这个索引。

操作系统和网络的选择题也占了不少比例。比如进程和线程的区别、死锁产生的四个必要条件、TCP三次握手和四次挥手的流程、HTTP和HTTPS的端口号差异。这些内容不需要你背得一字不差,但要在60秒内判断出哪个选项是对的。我当时就是因为在网络上花的时间多了,最后几道选择是蒙的,所以建议大家笔试前专门抽一两天把计算机网络和操作系统的常见考点快速过一遍,别把复习时间全押在算法题上。

2. 编程题题型拆解与思路复盘

2.1 OTA场景下的算法题:字符串处理和模拟

编程题里最让我有“去哪儿味”的一道,是把一个带时区的字符串时间转换成另一个时区的对应时间。题目给的是类似“2023-03-15 14:30:00 UTC+8”这样的输入,要你输出纽约时间的对应格式。这类题的本质是字符串解析加日期计算,LeetCode上找不到一模一样的原题,但和“Valid Number”“字符串转整数”是同一个套路。

拿到这种题,第一步不是急着写代码,而是先把输入格式拆清楚。比如时区偏移可能带半小时甚至四十五分钟,像印度时区就是UTC+5:30,如果你只处理了整小时偏移,后面用例就会挂。我当时的思路是:先用分隔符把年月日时分秒拆出来,再单独提取时区偏移量,统一换算成UTC时间戳,最后再按目标时区偏移量输出。用时间戳作为中间格式,能避免跨月、跨年时手动进位出错。

下面是一段面向笔试的简化版Java示意代码,核心思路就是用UTC秒数做中转:

import java.time.LocalDateTime; import java.time.ZoneOffset; public class TimeConverter { public static void main(String[] args) { // 模拟输入:"2023-03-15 14:30:00 UTC+8 -> UTC-5" String datetime = "2023-03-15 14:30:00"; int srcOffset = 8; // 源时区UTC+8 int targetOffset = -5; // 目标时区UTC-5 LocalDateTime ldt = LocalDateTime.parse(datetime.replace(" ", "T")); long utcSeconds = ldt.toEpochSecond(ZoneOffset.UTC) - srcOffset * 3600L; LocalDateTime targetTime = LocalDateTime.ofEpochSecond( utcSeconds + targetOffset * 3600L, 0, ZoneOffset.UTC); System.out.println(targetTime); } }

这段代码在笔试里属于“能过大部分用例但缺少边界校验”的版本。真要追求满分,还得处理输入里可能出现的非法时间、时区偏移大小为负号、以及秒数为闰秒等情况。不过在90分钟的笔试里,先把主干逻辑跑通,比一上来就堆异常处理要划算得多。

2.2 数据结构题:为什么优先用数组而不是集合

第二道编程题是一个数组题,题目大致是:给一个整数数组,找出连续子数组的最大和。这道题本质是经典的Kadane算法,最优解能做到O(n)时间、O(1)空间,LeetCode上有个原题叫“Maximum Subarray”。很多人一看是简单题就会放松警惕,但它的考点其实藏在空间复杂度里。

正确写法是维护两个变量:一个记录当前累加和cur,一个记录历史最大值max。每遍历一个数,cur = max(cur + num, num),max = max(max, cur)。代码非常短:

public int maxSubArray(int[] nums) { int cur = 0; int max = Integer.MIN_VALUE; for (int num : nums) { cur = Math.max(cur + num, num); max = Math.max(max, cur); } return max; }

有的候选人第一反应是先用前缀和数组,再枚举所有子区间,这样也能做对,但数组长度一旦到10^5以上就会超时。我之所以把这个题拎出来说,是因为它考察的不只是“你会不会写”,而是“你能不能想到用常量空间”。核心直觉是:如果当前累加和已经是负数,那它对后续加和只会有负面贡献,干脆从当前元素重新开始。这个思想和业务里的止损逻辑很像,理解透了,代码就不会忘。

另外,这套题里还出现过用ArrayList还是int[]的对比选择。性能敏感场景下,数组能节省装箱、扩容开销,笔试系统有时会用大数据量卡超时,所以能用原始数组就别用包装类集合,能用HashMap就别用ArrayList做查找。这个习惯在笔试和实际工作中都有价值。

2.3 并发和异步编程题:2023年绕不开的热门考点

这两年笔试和面试里,并发和异步编程的出现频率明显上升。去哪儿2023春招的选择题里虽然没有直接让写多线程代码,但出现了CompletableFuture异常处理的选项,这让我意识到,只会写单线程算法已经不太够用了。

这里顺便说一下CompletableFuture的异常处理,因为这是网上问得最多的点。如果你在异步任务里抛了异常,而主线程没有做任何处理,这个异常很可能会被“吞掉”,程序悄悄失败,排障时非常难受。正确做法是给CompletableFuture链上加exceptionally,或者用handle统一处理正常结果和异常。写成代码大概是这样:

CompletableFuture.supplyAsync(() -> { if (Math.random() > 0.5) { throw new RuntimeException("任务失败"); } return "ok"; }).exceptionally(ex -> { System.err.println("捕获异常:" + ex.getMessage()); return "fallback"; });

笔试阶段不太可能要求你完整实现一个线程池,但至少要看清核心参数的含义:核心线程数、最大线程数、阻塞队列长度、拒绝策略。这几个参数经常被组合起来考,比如“核心线程数5,最大线程数10,队列容量100,来了200个任务,最后一个任务会被执行还是拒绝”。很多人死记硬背,但没有理清执行顺序:先把任务放进队列,队列满了才增加线程,线程数到上限才触发拒绝策略。搞明白这个流程,这类选择题基本就是送分题。

异步编程准备方面,我给出的建议是:先掌握Runnable、Callable、Future这些基础抽象,再学Executor和线程池,最后才看CompletableFuture。如果急着上CompletableFuture,又不理解线程池模型,遇到异常处理、任务依赖、超时取消这些问题时,很容易一头雾水。

3. 笔试现场:语言选择、时间分配和在线编辑器

3.1 语言选择:Java、Python还是C++

去哪儿笔试系统支持Java、Python、C++、Go等主流语言,开始前会先让你选定一门,之后所有编程题都用这门语言提交。我当时选的是Java,一是因为后端团队技术栈就是Java,二是因为Java的集合框架在笔试阶段写起来比C++省心,而Python虽然代码量最短,但有些在线笔试系统对递归深度有限制,深搜类题目容易莫名其妙爆栈。

如果你还在纠结选什么语言,我的建议是:用你最有把握的那门,而不是用“最近看起来最流行”的那门。笔试时间很紧张,切换语言带来的语法错误比切换算法思路更容易毁掉全场。如果岗位方向明确要求了语言,比如Java后端,那你就优先把Java并发布局、JVM基础、Spring常用注解这类八股准备扎实,编程题也用Java写。

另外,如果你选择C++,笔试前一定把STL的几个常用容器过一遍:vector、map、unordered_map、set、string的基本操作和复杂度。C++在内存管理和裸指针上容易出bug,但运行效率高,适合图论、动态规划这类大规模数据题。Go的话,要注意在线系统里Go版本差异,有些新语法在旧版本编译器上不认。

3.2 90分钟的时间分配和做题顺序

我拿到试卷后的第一件事,不是从头开始做,而是先把四道编程题都扫一遍。这个动作很重要,它能让你在脑子里快速形成一个难度排序。当时我扫完发现:第一道是字符串时区转换,第二道是连续子数组最大和,第三道是图论邻接表遍历,第四道是动态规划。我的做题顺序是:先做第一、二道,再做第三道,最后剩多少时间就给第四道随缘。

时间分配上,我定的规则是:选择题最多20分钟,编程题每道最多15到20分钟,超时就立刻止损,跳到下一道。很多候选人挂在“非要把某道题AC”这个执念上,结果最后一道简单的题都没时间写。笔试不是竞赛排名,而是分数达标,多拿一道题的分数,往往比死磕一道难题更有价值。

这里还要注意“部分分”策略。如果一道题的完整解法想不出来,至少把暴力解写出来并提交,只要它能过部分测试用例,平台就会给你对应的分数。比如动态规划那题,我最后只写了一个递归版本,提交后大概过了30%的用例,总比交白卷强。在真实笔试里,这一两分的差距可能就决定你能否进入下一轮。

3.3 在线编辑器里容易被忽略的细节

在线编辑器是老生常谈但永远有人踩坑的地方。有些平台要求你写main方法,有些平台只要求你补全一个函数;有些平台用tab缩进,有些平台用空格;有些平台对类名有严格要求,比如Main类首字母必须大写。这些细节看着小,一旦编译报错,花掉的时间远超想象。

我考场上做的第一件事,是先花两分钟做一次“空编译提交”:把模板代码原样提交一次,看它能不能通过编译。这个操作帮我发现了两个问题:一是系统模板里已经包含了部分import,但提交时不会自动带上,所以自己的import一定要写全;二是主类名默认是Main,如果新建了一个类名,必须要和文件名保持一致。这个习惯在后来好几场笔试里都救过我。

另外,在线编辑器通常没有代码格式化插件,缩进和括号对齐全靠手动,写的时候稍微注意一下变量命名和结构层次,免得后续调试时看得眼晕。我习惯先把空方法签名和输入读取写好,再往里面填核心逻辑,这样即使最后没时间写完,也能保证编译通过,拿一个“编译正确”的最低分。

4. 准备路线:从刷题到契合岗位的进阶训练

4.1 按公司风格刷题,而不是盲目刷题

不少人问春招编程笔试到底怎么准备,我的回答是先从公司业务特点倒推题目方向。去哪儿是在线旅游平台,核心业务包括机票、酒店、火车票、度假产品,这些业务在技术层面的共性是:订单状态流转复杂、库存和价格实时变动、并发量大、对接口的可用性和性能要求高。所以笔试题通常不会脱离数组、字符串、哈希表、图遍历这几个方向,最多再加一道动态规划压轴。

具体刷题时,我建议按“专题+限时”的方式推进,而不是按题号顺序刷。比如给自己定一个周期,这一周只刷数组双指针和滑动窗口,下一周只刷动态规划。每做完一道题,在笔记里记下解法和复杂度,并尝试分析这个题目类型如果放在一个在线旅游场景里,会对应什么问题。这样做的好处是,在笔试现场看到题目时,你能更快联想到相似的做题经验。

“专题+限时”还有一个好处:它能帮你模拟真实考场节奏。比如每天花45分钟,做两道中等题+一道困难题,时间到就停笔,然后复盘。久了之后,你对“一道题大概需要多久”会有精确的体感,考场上就不会在某道题里死磕太长时间。

4.2 异步编程、AI编程和工具链的补充学习

除了算法题,2023年还有一个绕不开的话题就是AI编程工具。笔试前一个月,Cursor已经比较火了,我也尝试过用它来辅助刷题,比如让AI解释LeetCode题解里某一段状态转移方程的含义。这里想说一个比较现实的观点:笔试现场不能用Cursor,也不能用Copilot,但准备阶段可以用它们来辅助建立题感,尤其是你把一道题卡在原地超过20分钟的时候,让AI用自然语言讲一遍,效率会高很多。

不过有个副作用,就是容易让人产生“我随时能调AI,手写能力不重要”的错觉。我在春招期间的约定是:AI只做题解解释和错误分析,不直接帮助写题;一道题如果我先看了题解,那一定会合上答案,再独立默写一遍。准备阶段常见的AI提示词我会这么写:“请用自然语言解释这段动态规划的转移方程,并指出边界条件”,或者“帮我检查这段代码在空数组输入下会不会越界”。这类提示词能快速提醒你注意易错点,而不是直接把答案抄下来。

如果你对异步编程和AI编程工具两件事都有兴趣,可以做一个组合练习:让AI生成一个异步任务处理的示例代码,然后你自己手写一个带异常捕获和超时控制的版本,最后对比差异。这个过程既练了CompletableFuture,也练了代码阅读和评审能力,比干刷题有意思得多。

4.3 接触分布式MapReduce和HDFS的必要性

如果你投的是大数据相关岗位,那编程笔试之外,还要准备MapReduce和HDFS的基础概念。去哪儿这类公司的日志分析、用户行为数据统计,都会用到Hadoop生态。笔试不一定直接考,但你的简历里如果写了大数据项目,面试官大概率会顺着项目往细节问。

MapReduce不需要你从零实现完整框架,但至少要知道Map阶段和Reduce阶段各自负责什么,Shuffle过程发生在哪个位置,数据倾斜怎么处理。一个经典的例子就是词频统计:Map阶段把每个单词变成(word, 1)的键值对,Shuffle阶段按key分组,Reduce阶段把相同key的value求和。听起来很简单,但真正要落地时,需要考虑数据分布不均、合并逻辑优化、磁盘IO开销等问题。

HDFS则要掌握NameNode和DataNode的职责、默认副本数为什么是3、适合存储大文件但不适合大量小文件的场景。这些概念在笔试里经常以选择题形式出现,比如“一个128MB的文件块,默认复制3份,总共占多少物理存储”。理解了副本机制,这类题就是简单乘法。

5. 典型坑位与排查技巧实录

5.1 同一段代码,本地没问题,笔试系统却超时

在线笔试里最典型的翻车现场是:本地IDE跑测试用例全过,一提交就开始超时。超时原因主要有三类:第一类是时间复杂度过高,数据量一大就崩;第二类是死循环或递归深度过大;第三类是频繁创建对象、频繁使用字符串拼接,导致GC压力过大。我当时就有一次在循环里用了String +=拼接,数据量小的时候完全没问题,数据量到10^5级别就直接超时。

排查超时问题,先别急着换算法,而是去检查循环里有没有无意识的O(n²)操作。比如Java里list.contains(),在ArrayList上是线性扫描,如果你在一个外层循环里频繁调用它,整体复杂度就可能变成O(n²)。这种情况下,换成HashSet往往立竿见影。另外,如果用到递归,要检查递归树是不是重复计算了大量子问题,如果是,就改成带记忆化的动态规划。

为了保险,我在每次提交前都会额外做一个“边界自测”:空数组、只有一个元素、最大长度随机数组、全负数数组。这四类输入能覆盖大多数边界问题,比如Kadane算法里如果max初始值设成0,遇到全负数数组就会出错。

5.2 输入输出格式:卡住无数人的隐形杀手

很多时候,题目本身不难,难在看不懂输入格式。有些题目是多行输入,但每行的字段分隔符可能是空格、逗号或制表符;有些题目要求输出浮点数并保留两位小数,有的要求四舍五入,有的要求直接截断;还有些题会把多组测试数据放在同一个文件里,要求用while(scanner.hasNext())循环读取,如果你只读了一组数据,平台会判定只过了部分用例。

我吃亏之后总结了一份checklist:先看输入样例有几行,每行几个字段,字段之间用什么分隔;再看输出样例是整数、浮点数还是字符串,浮点数要保留几位;最后看题目有没有明确写“多组输入”或“直到文件末尾”。这三个问题确认清楚再写代码,能避免大量无谓的提交错误。

在线编辑器里调试输入格式比较别扭,因为你没法在本地随意构造文件。我的做法是先把样例输入写成字符串常量,代码里直接读取该字符串测试逻辑;等本地跑通后,再改成从标准输入读取。这样做的好处是把“数据结构解析”和“算法逻辑”拆开验证,定位bug会快很多。

5.3 数组越界和空指针的快速定位

限时场景下,空指针和数组越界最烦人,因为报错信息只给一个行号,而线上平台又常常不允许你打断点。我常用的定位方式是“二分打印法”:根据报错行号,在关键变量和数组下标附近加print,不断缩小定位范围。虽然多花一点时间,但比对着代码瞎猜靠谱得多。

另外,我给自己定了三条硬规则:凡是涉及数组下标的题,循环一律用i < n,而不是i <= n;凡是涉及字符串处理的题,先判空再处理;凡是涉及递归的题,先确认终止条件能覆盖所有边界输入。这三条规则看起来很简单,但它们能规避的报错远超想象。

给一个常见问题速查表,笔试前扫一眼很管用:

| 报错特征 | 常见原因 | 快速排查方向 | | 数组越界 | 循环边界写错,或下标从1开始但数组从0开始 | 打印当前下标,统一从0开始 | | 空指针 | 集合或对象未初始化,或访问了不存在的key | 先判空,再取值 | | 超时 | 复杂度太高,或循环里隐藏O(n²)操作 | 用小数据集测边界,再分析复杂度 | | 输出格式错误 | 多打了空格,或小数位不对 | 与样例输出逐字符对比 |

5.4 编译错误和类名命名问题

在线笔试最常见也最憋屈的错误是编译失败。有些平台要求主类名为Main,有些平台则允许你自定义类名,但每道题的模板可能都不一样。如果你在编辑器里新建了一个类,记得把类名改成题目要求,否则提交时会直接编译错误。

有些平台对包名也有要求,模板开头可能有一行package,千万不要删掉,也不要手贱改成别的包名。有些平台会要求提交纯代码,不能带有注释里的特殊符号,所以在线的中文注释里尽量避免带分号、花括号等项目符号。

6. 复盘:一场笔试下来,最该带走什么

6.1 分数之外,笔试暴露了哪些能力短板

笔试结束不等于结束,真正的成长在复盘。我的做法是,趁记忆还热,把每道题的思路写进笔记,并标注自己属于哪一种:完全没有思路、想到思路但没写完、写完了但bug一大堆。这样拆开之后,后续复习就很有针对性:没思路说明算法模型积累不够,得补专题;没写完说明代码熟练度不行,得练手速;写完有bug说明边界处理能力不足,得多做小数据自测。

这里也想说下企业筛选时的真实逻辑。虽然叫“编程笔试”,但公司要考察的不只是你会不会写代码,还包括代码风格是否规范、变量命名是否有意义、有没有基本注释、有没有考虑异常场景。这些细节在编程题横向对比时非常明显。两个人代码都能跑通,但一份变量名全是a、b、c,另一份拆成userIndex和maxSubSum,后者的观感和可信度会高很多。

我在复盘时还会用AI编程助手再造一遍题目场景。具体做法是:给AI一段我自己写的笔试代码,让它用code review的视角指出可读性、边界处理和复杂度问题。这比自己刷答案更能暴露盲点,尤其是“变量命名不清晰”和“没有考虑null输入”这类问题,AI通常能直接点出来。

6.2 一些长期有效的方法论

春招笔试这个阶段比较特殊,你既要应付算法题,又得准备语言特性和计算机基础,还要分出精力关注行业里的新工具、新实践。我的经验是把它分成两条主线并行推进:一条是数据结构与算法,另一条是Java并发、操作系统、计算机网络、数据库基础。两条线每周都推进,比把一本书啃完再换另一本更容易对抗遗忘。

如果时间紧张,至少要保证自己在90分钟内能完成两道中等难度热身题,再留时间做一道动态规划或图论题。做到这个水平,通过率就已经高过大部分在职和在校候选人了。平时刷题时,尽量用没有自动补全的环境,逼自己在裸写代码情况下保持手感,笔试现场的很多紧张感,其实都源于对手写代码不熟悉。

最后再分享一个小技巧:笔试结束后,不管结果如何,都把题目描述、自己的代码、标准解法三样保存到一个统一文件夹里。春招可能连续投多家公司,很多题目类型会重复出现,比如时区转换、连续子数组、订单状态统计,这些沉淀下来的资料,会在之后的秋招或实习申请时变成你独有的题库。我当时就是这样积累了几十份实战复盘,第二场笔试明显比第一场稳多了。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询