
1. 整卷拆解先搞懂这套题在考什么瓜子二手车2019秋招研发笔试卷2整套题给我的感觉非常“正统”——没有偏题怪题但覆盖面很广。算法、Java基础、数据库、网络、系统设计基本都有涉及属于典型的互联网公司校招笔试题型组合。如果你是在准备秋招或者想检验一下自己的基础功底这套卷子值得认真做一遍。先看整卷布局题型大致分四块单选题、多选题、编程题、简答题。单选题和多选题主要考察计算机基础操作系统、网络、Java集合类、JVM编程题一般是一到两道算法题简答题则是数据库SQL编写或者系统设计类问题。从我接触过的多套同级别试卷来看这个结构几乎是标准模板不会有太多意外。为什么瓜子这种偏传统互联网业务的公司会出这种卷子因为二手车交易平台的核心业务场景是“车源信息管理 用户端搜索/推荐 订单交易流程”底层大量依赖数据库的CRUD、缓存的使用、并发场景下的数据一致性。所以笔试试卷通常会重点考察这几个维度算法与数据结构考察逻辑思维和编码能力尤其关注链表、数组、字符串、动态规划这些高频考点。Java并发与集合原理考察对常用类库的理解深度比如HashMap、ConcurrentHashMap、线程池。数据库与SQL能力考察对索引、事务、多表关联查询的熟练度这是业务开发的基本功。系统设计思维给一个具体业务场景看你怎么拆分模块、选型技术栈、处理边界问题。明白这层逻辑之后你就知道这套卷子不是简单刷题而是通过题目筛选出“能干活、懂原理、有潜力”的候选人。下面我把每一类题型的代表题目和对应的解题思路拆开讲重点放在编程题和SQL题上因为这两块分值高、拉分大。2. 编程题精讲两道高频算法的完整推导2.1 题目一数组中的最大连续子数组和这道题在笔试卷中出现频率极高几乎可以算是秋招算法题的“入场券”。题目描述很直接给定一个整数数组找出一个具有最大和的连续子数组子数组最少包含一个元素返回其最大和。比如输入[-2, 1, -3, 4, -1, 2, 1, -5, 4]最大和的连续子数组是[4, -1, 2, 1]和为 6。我第一次见这道题时用了暴力解法两层循环枚举所有子数组时间复杂度 O(n^2)在当时的数据规模下能跑通但面试官会追问一句“能不能优化” 这道题的标准解法是动态规划核心思路是维护一个“以当前位置结尾的最大子数组和”的状态变量。具体推导过程是这样的假设dp[i]表示以nums[i]结尾的连续子数组的最大和那么对于第 i 个元素只有两种选择一是把它接到前一个子数组后面dp[i-1] nums[i]二是让它自己单独成为一个子数组nums[i]。取两者的较大值就是dp[i]。最终答案就是所有dp[i]中的最大值。写成代码就是public int maxSubArray(int[] nums) { int maxSoFar nums[0]; int maxEndingHere nums[0]; for (int i 1; i nums.length; i) { maxEndingHere Math.max(nums[i], maxEndingHere nums[i]); maxSoFar Math.max(maxSoFar, maxEndingHere); } return maxSoFar; }这个解法的时间复杂度是 O(n)空间复杂度可以优化到 O(1)。很多人在笔试中丢分是因为只写出了动态规划版本却没有说明状态转移方程的由来。我的建议是在代码注释里写明状态定义和转移逻辑阅卷人一眼就能看到你的思路。这道题还有一个变体要求输出最大子数组的起始和结束下标。处理方式是在状态更新时记录当前子数组的左边界当maxEndingHere被更新为nums[i]时左边界重置为 i当maxSoFar被更新时记录当时的左右边界。这个变体考察的是边界处理和状态记录的精细度代码量不大但容易在细节上翻车。2.2 题目二链表反转的递归与迭代两种实现链表反转是笔试中另一道高频题。题干很简单反转一个单链表比如1 - 2 - 3 - 4 - 5变成5 - 4 - 3 - 2 - 1。这道题难在候选人经常只准备了一种写法一旦面试官要求换一种实现方式就卡壳。迭代实现的思路是用prev指向当前节点的前一个节点curr指向当前要处理的节点next暂存下一个节点然后循环执行“断开当前节点的 next 指针并指向前一个节点”的操作。代码如下public ListNode reverseList(ListNode head) { ListNode prev null; ListNode curr head; while (curr ! null) { ListNode next curr.next; curr.next prev; prev curr; curr next; } return prev; }递归实现的思路稍微抽象一点假设当前节点之后的链表已经反转完成那么只需要把当前节点的下一个节点的 next 指向当前节点然后把当前节点的 next 置空。递归的出口是链表为空或只有一个节点时直接返回。public ListNode reverseList(ListNode head) { if (head null || head.next null) { return head; } ListNode newHead reverseList(head.next); head.next.next head; head.next null; return newHead; }笔试答题时我一般会先写迭代版本因为不容易出现栈溢出而且逻辑清晰。但如果时间充裕我会在注释里强调两种方式的区别迭代是原地调整指针空间 O(1)递归依赖系统栈空间 O(n)。这道题的延伸考点是“反转链表的前 N 个节点”或“K 个一组反转链表”属于进阶变体但基础版一定要拿到满分。写完后建议自己用三个节点的链表在草稿纸上走一遍流程确认指针没有丢。3. Java 基础与并发单选题背后的原理深度3.1 HashMap 的底层结构与扩容机制秋招笔试的单选题里HashMap 基本是必考内容。常见的考法有几种问默认初始容量是多少16、加载因子是多少0.75、什么时候会触发扩容元素数量超过 容量 × 加载因子、JDK 1.8 之后引入了什么优化链表转红黑树树化阈值是 8退化阈值是 6。很多人会背这些数字但一问“为什么加载因子是 0.75”就答不上来。这个值的确定其实是在空间和时间之间取一个平衡。如果加载因子太大比如 1.0空间利用率高但哈希冲突会变多链表变长查询效率下降如果太小比如 0.5冲突少但浪费大量内存。0.75 是 JDK 开发者在多种真实负载下测试出来的一个折中值。答题时如果能说出这层含义比干巴巴地背数字要加分。还有一个高频考点HashMap 为什么线程不安全主要原因是多线程同时 put 时可能造成数据覆盖JDK 1.8 中即使不会形成环形链表仍存在 put 覆盖问题以及扩容时多个线程同时操作同一个桶数组导致的数据不一致。笔试题里如果问“线程安全的替代方案”答案是ConcurrentHashMap或HashTable但你要能说清 ConcurrentHashMap 在 JDK 1.8 中放弃了分段锁改用 CAS synchronized 锁住桶的头节点粒度更细并发度更高。3.2 线程池的核心参数与执行流程另一类常考题目围绕线程池展开。标准问题就是“ThreadPoolExecutor 的核心参数有哪些任务提交后执行流程是什么” 核心参数是corePoolSize核心线程数、maximumPoolSize最大线程数、keepAliveTime非核心线程空闲存活时间、workQueue任务队列、threadFactory线程工厂和RejectedExecutionHandler拒绝策略。执行流程用一句话概括就是先让核心线程干活核心线程不够就排队队也满了就开临时线程到最大线程数还是不够就触发拒绝策略。这个流程在笔试题中经常用“线程池收到第 N 个任务时会发生什么”来考比如核心线程数是 2队列容量是 3最大线程数是 5依次提交 6 个任务前 2 个任务直接创建核心线程第 3、4、5 个任务进入队列排队第 6 个任务会创建临时线程去执行。很多候选人在这类题上丢分原因是把“队列满后创建临时线程”和“直接创建最大线程数”的顺序搞混了。还有一个容易忽略的点是拒绝策略的四种类型AbortPolicy抛异常、CallerRunsPolicy调用者线程执行、DiscardPolicy直接丢弃、DiscardOldestPolicy丢弃队列中等待最久的任务。笔试题中常考的是 AbortPolicy 在默认情况下的使用以及实际项目中为什么要选用 CallerRunsPolicy因为它在任务过多时不会丢任务只是把压力回传给调用方起到天然限流的作用。4. 数据库与 SQL二手车业务场景的实战写法4.1 多表关联查询找出各品牌在售车源的平均价格瓜子二手车是一个强数据驱动的平台SQL 能力是研发岗的基本功。笔试卷中大概率会出现一道 SQL 编写题一般会给出车辆信息表、品牌表、车源状态表等要求写出指定统计结果的查询语句。我见过的一道典型题目是已知vehicle表包含字段id、brand_id、price、statusbrand表包含字段id、brand_name要求统计每个品牌下所有“在售”status on_sale车源的平均价格并且只显示平均价格大于 10 万的品牌按平均价格降序排列。这道题的考点有三个JOIN 关联、GROUP BY 分组、HAVING 过滤。很多人会写下 WHERE status on_sale然后忘了 GROUP BY 之后必须用 HAVING 来过滤聚合结果。正确写法如下SELECT b.brand_name, AVG(v.price) AS avg_price FROM vehicle v INNER JOIN brand b ON v.brand_id b.id WHERE v.status on_sale GROUP BY b.brand_name HAVING AVG(v.price) 100000 ORDER BY avg_price DESC;注意几个细节第一GROUP BY 后面建议使用b.brand_name而不是b.id因为 SELECT 中的非聚合列必须出现在 GROUP BY 中某些数据库对brand_id和brand_name的对应关系会有严格校验。第二WHERE 和 HAVING 的执行顺序是先过滤行再分组所以状态过滤条件放在 WHERE 中效率更高不要放在 HAVING 里。如果题目再进一步要求“每个品牌下在售车源数量大于 3 的才参与统计”那你需要在 HAVING 中再加一个COUNT(v.id) 3的条件。需要注意的是COUNT(v.id)不会统计 NULL 值如果你用COUNT(*)会把这一整行都算进去两者在某些特殊数据下会有差异建议明确指定计数字段。4.2 索引失效的典型场景SQL 题后面往往跟着一道索引相关的选择题比如问“以下哪种写法会导致索引失效”。我整理几个高频选项在索引列上进行函数计算如WHERE YEAR(create_time) 2020、隐式类型转换如索引列是 varchar 类型却传入数字、使用 LIKE 前缀模糊匹配LIKE %abc、使用 OR 连接非索引列。这些知识点说起来简单但笔试中经常用“给定一个 SQL判断它会走全表扫描还是索引”来考察。我的经验是看到 WHERE 子句中有函数、条件左侧有计算表达式时基本可以判断索引失效。另外OR 两边的字段如果有一个没有索引整个查询可能弃用索引。如果无法确定是否走了索引可以在答案里补充“用 EXPLAIN 查看执行计划”这会让阅卷人觉得你是有实战经验的。延伸一下对于二手车这种数据量较大的业务场景车源列表页通常会有很多筛选条件品牌、价格区间、城市、车龄、里程如果每个条件都建独立索引实际执行时未必能用上。更合理的方案是建立覆盖索引或者让 DBA 通过慢查询日志分析后决定组合索引顺序。笔试时如果能把这种经验写进简答题答案里会很加分。5. 简答与设计题考察系统设计思维5.1 热点车源的缓存设计简答题里经常出现一类设计类问题对二手车业务来说典型的题目是“假设平台上有大量用户同时浏览同一辆热门车源比如一辆价格极具吸引力的二手车你怎么设计这个详情页的访问链路来保证响应速度和数据一致性”这道题考察的核心是缓存穿透、缓存击穿、缓存雪崩三个概念。先解释一下缓存穿透是查询一个数据库里根本不存在的数据每次请求都打到数据库缓存击穿是某一天热 key 在缓存的过期瞬间大量请求同时涌入数据库缓存雪崩是大面积缓存同时过期导致数据库压力瞬间飙升。处理方案分三块来说。对于缓存穿透可以在查询结果为空时也缓存一个空值并设置较短的过期时间或者使用布隆过滤器在缓存前过滤掉一定不存在的 key。对于缓存击穿核心做法是热点数据不设置过期时间逻辑过期或者在缓存失效时使用“互斥锁”保证只有一个线程去数据库重建缓存其他线程等待。对于缓存雪崩可以在原有过期时间基础上增加一个随机值避免大量 key 在同一时刻集体过期同时可以采用多级缓存本地缓存 Redis来兜底。答题时要特别注意不要只堆名词要结合题目场景。比如你可以说“对于这个热门车源我先在 Nginx 层做一层本地缓存失效后再回源 RedisRedis 也没有时再走数据库并且用分布式锁控制只有一个请求去查库回填缓存。” 这种链路描述比单纯背概念更有说服力。5.2 车辆搜索列表的接口设计另一个常见的简答题是“设计一个车辆搜索列表页的后端接口”。这类题的答题框架基本是先定接口入参关键词、城市、品牌、价格区间、排序方式、分页参数再定返回结构车源 id、标题、图片、价格、里程、上牌时间然后考虑性能和稳定性。关键点在于分页方案。传统LIMIT offset, size在深分页场景下性能极差因为 MySQL 需要扫描前 offset 条记录再丢弃。笔试题如果问“一个 500 万条数据的车源表怎么高效分页”比较好的答案是使用游标分页客户端传上次查询结果的最后一辆车源的 IDSQL 里用WHERE id ? ORDER BY id LIMIT ?。这样做的好处是即使翻到很后面的页查询速度也不会有明显下降。排序问题也要考虑。如果用户按价格排序但是同一价格区间内的数据非常多需要加一个稳定排序的字段作为 tie-breaker比如ORDER BY price DESC, id DESC否则会出现同一页数据在前后两次请求中重复出现或遗漏的情况。这个细节很多候选人答不上来面试官问一句“为什么价格排序还需要加 id 排序”就能筛选出是否有实战经验。5.3 并发场景下的预约看车功能如果试卷难度偏高还可能出现带并发属性场景的简答题。比如“多个用户同时预约同一辆车的看车时间怎么保证不超卖” 这本质上是一个库存扣减问题和秒杀系统很相似。我一般给出的方案是在数据库层面使用乐观锁车辆表加一个version字段扣减预约名额之前先查出version更新时在 WHERE 条件中加上version 旧值如果更新的影响行数为 0说明期间有其他请求抢先修改就提示用户“该时段已被预约”。这个方案比用synchronized锁在本机内存里靠谱得多因为在分布式部署下多台机器各持一把锁根本无法保证互斥。更进一步如果预约量极大可以把预约次数先放 Redis 里用DECR命令做预扣成功后再异步写 MySQL。但我会保守一点在笔试答案里说明“Redis 预扣存在最终一致性延迟需要配合消息队列或定时任务对账”——这会让阅卷人觉得你不只懂方案还知道方案的边界和风险。6. 高分答题技巧与复盘建议6.1 笔试现场的时间分配策略整套试卷的时间一般在 90 到 120 分钟之间题量在 30 到 40 道之间。我的经验是先做编程题再做数据库题最后做选择题。原因是编程题分值最高且最需要头脑清醒如果放到后面前面选择题已经消耗了大量脑力很容易写不出 bug-free 的代码。编程题我给自己定的规则是先花 2 分钟读题5 分钟确定思路10 分钟写码5 分钟检查和测试用例。如果 10 分钟还没完整思路先写一个暴力解法保底不要死磕最优解。笔试阅卷不完全只看结果如果暴力解法思路清晰也能拿到一部分分数空着不写才是最大的损失。对于选择题遇到模棱两可的题目先用排除法把确定的选项标记出来不确定的不要消耗太多时间整套题做完后再回头处理。单选和多选一定要看清题干是“正确的是”还是“错误的是”我见过太多人在这种地方栽跟头明明知识掌握到了却因为审题不仔细而丢分。6.2 经典题目复盘清单笔试结束后的复盘比刷题本身更重要。我建议按下面的清单做一次自检每一题都问自己三个问题这道题考的是什么知识点我为什么错了正确的思路应该怎么走高频复盘清单HashMap 在 JDK 1.7 和 1.8 之间的头插法/尾插法区别ArrayList 和 LinkedList 在增删查改上的复杂度差异线程的wait()与sleep()对锁的释放行为MySQL 中CHAR与VARCHAR的存储与性能差异事务的四大隔离级别及脏读、不可重复读、幻读的对应关系TCP 三次握手和四次挥手中的状态变化每一道错题都值得单独整理成笔记。我不建议把整张卷子重新完整抄一遍那样时间成本太高重点记录解题思路的关键一步和易错点就够了。6.3 同类题型的持续练习方向如果你手头没有更多同类试卷可以用 LeetCode 的 Top 100 高频题作为日常训练素材配合牛客网的 Java 专项练习来提高选择题正确率。编程题方面优先级从高到低排列如下链表相关反转、合并、相交、环检测二叉树遍历前中后序、层序、最近公共祖先动态规划子数组、子序列、背包问题字符串操作最长回文子串、无重复字符的最长子串排序与 Top K 问题快排、堆排、快速选择每天 2 道新题加 1 道旧题复盘的节奏坚持一个月就能看到明显变化。说到底笔试题考的不是“你做过多少题”而是“你有没有形成一套稳定的思维框架”。每道题都能从题干里抽出考点、匹配到对应解法、写出干净代码这套试卷就不会成为你的拦路虎。我在实际刷题和带新人的过程中最深的一个体会是笔试题的覆盖面再广核心考察的还是基础能力。与其去押题、背题不如把 Java 集合原理、并发机制、SQL 查询优化这些地基打牢再配合适量的题目练习效果会稳定得多。二手车业务的笔试题目虽然结合了业务场景但底层还是这些通用的研发基本功。把上面这些题型吃透比海量刷题更有价值。