瓜子二手车2019秋招编程题复盘:字符串、链表与动态规划全解析 📅 发布时间:2026/8/29 13:44:02 👁 浏览次数: 各大公司的秋招编程题总是能看出一些有意思的信号尤其是像瓜子二手车这种业务模型比较有辨识度的公司题目往往不会太偏但会在基础题里埋一些值得琢磨的细节。我花了一晚上把2019年这波题整理了一遍发现它们对数据结构基础、边界条件处理、以及把业务场景翻译成代码逻辑的能力考察得很实在。这篇文章就把我复盘后的完整版本分享出来每道题都配上思路拆解、代码实现和踩坑提示适合正在准备校招、或者想通过真实企业题目检验一下自己代码功底的朋友。1. 先搞清楚这次秋招编程题在考什么1.1 整体风格与难度分布瓜子二手车2019秋招的编程题整体走的是“稳中有变”的路线不像字节跳动那样动不动就是hard级别的动态规划加状态压缩也不像某些银行系公司考一堆冷门API记忆。它的题目重心集中在三块字符串处理、链表和数组操作、基础动态规划。从难度分布看简单到中等偏易的题目占比大概六成剩下四成是中等难度压轴题通常是一道需要想清楚状态定义的动态规划或者是一道带有业务包装的模拟题。这个比例对我这种刷题量一般的普通人来说其实挺友好只要把LeetCode前200题中的数组、字符串、链表、二叉树基础题吃透再专门练一下DP的常见模型基本就能应付。还有一个特点值得注意这家的题目题干往往较长喜欢把问题包装成一个“二手车交易场景”。比如价格区间合并、优惠券使用顺序、车辆热度排序这类描述本质都是经典的数组算法换了个马甲。面试官想看的就是你能不能快速把业务语言翻译成数据结构语言这种能力在实际工作中比会背几百道题更值钱。1.2 为什么这些题值得反复练一个很现实的原因是这些题代表了一类“面试通用货币”你练的不只是瓜子这一家公司的题目而是所有二三线互联网公司和垂直领域头部公司都会用的筛选维度。字符串、链表、DP是技术面试的三大常青树无论校招还是社招无论前端后端面试官都倾向于用这些题来快速判断候选人的代码功底。另一个原因是这些题目的区分度设计得相当精准。以字符串反转为例看起来人人都会但加上“按单词反转且保留空格格式”的限定条件后能在一遍AC的人立刻少一半。这种看似简单但暗藏边界条件的题恰恰是大厂筛选简历镀金者的利器。把这类题练到条件反射的程度你在真正的笔试现场就能把宝贵的思考时间留给更难的题。对我来说最受用的其实是那些带有业务背景的模拟题。这种题在LeetCode上不一定找得到完全一样的原题但换汤不换药。练会了这些你在面试官面前展示的不只是“我会写代码”更是“我能把抽象的问题落地成工程代码”这个印象分往往比AC几道题重要得多。2. 字符串与模拟题原题解析2.1 字符串反转类一个经典题的四个层级字符串反转是2019年秋招笔试中出现频率极高的题目瓜子二手车也考了而且不止一种考法。第一层是“反转整个字符串”这个最简单直接用双指针从头尾往中间交换即可。第二层是“按单词反转”要求把一段英文句子中的所有单词顺序颠倒但单词内部的字母顺序保持不变。第三层是“反转每个单词内部顺序但单词顺序不变”。第四层则要求“只反转字母数字和其他字符保持原位”。我当时在笔试中遇到的是第二层题目描述大致是“输入一句英文单词之间以单个空格分隔句子前后没有多余空格请将单词顺序完全反转并输出”。def reverse_words(sentence: str) - str: words sentence.split( ) return .join(words[::-1])这个写法虽然能AC但后来我在复盘时想到一个更稳的思路先整体反转整个句子然后双指针逐个单词再反转回来。这样做的好处是不依赖语言内置的split和切片在C/C这种需要手动管理内存的语言里更通用而且可以原地完成。void reverse_words(char *s) { // 先反转整个字符串 reverse(s, s strlen(s) - 1); // 再逐个单词反转 char *start s; for (char *p s; ; p) { if (*p || *p \0) { reverse(start, p - 1); if (*p \0) break; start p 1; } } }这道题最容易踩的坑有两个。一个是忽略了多空格的情况题目如果没说明“以单个空格分隔”你就得考虑连续空格的容错。另一个是字符串尾部可能有换行符如果用gets或者python的input()要注意把换行符处理干净。2.2 业务包装的模拟题二手车价格区间合并这道题是这一批题目里最有“瓜子味”的一道题干大意是“平台收集到多个车辆的报价区间每个区间用[a, b]表示a小于等于b可能存在区间重叠请合并所有重叠区间输出合并后的区间列表。”其实这就是LeetCode 56题“合并区间”的经典解法。我的第一反应是排序加贪心扫描先按区间起点排序然后遍历每个区间如果当前区间的起点小于等于上一个合并区间的终点就扩展合并区间的终点否则开启一个新的合并区间。def merge_intervals(intervals): if not intervals: return [] # 按区间起点排序 intervals.sort(keylambda x: x[0]) merged [intervals[0]] for i in range(1, len(intervals)): cur_start, cur_end intervals[i] last merged[-1] if cur_start last[1]: # 有重叠合并终点取较大值 last[1] max(last[1], cur_end) else: merged.append([cur_start, cur_end]) return merged做题时容易忽略的是“相邻也算重叠”这种边界场景比如[1, 2]和[2, 3]按题目要求是应该合并成[1, 3]的。还有输入数据可能是乱序、区间可能是浮点数、甚至可能出现起点大于终点的非法输入笔试时虽然不会考得太变态但养成先和面试官确认边界条件的习惯会显得你很有工程意识。这类题放到真实业务里非常实用二手车平台经常会遇到多个来源的车辆价格数据去重合并之后才能做统一展示。所以面试官考这道题不光是为了考排序和贪心也是在暗示你他们对候选人的数据清洗能力有期待。3. 数组与链表的操作题3.1 合并两个有序链表迭代与递归的取舍链表题在笔试中占比不低2019年这批题里出现了“合并两个升序链表”的经典题目。题干描述大概是“有两个已经按车辆编号升序排列的链表请将其合并为一个升序链表后返回。”这道题最经典的解法是迭代法用一个虚拟头节点来简化边界处理。class ListNode: def __init__(self, val0, nextNone): self.val val self.next next def merge_two_lists(l1: ListNode, l2: ListNode) - ListNode: dummy ListNode() cur dummy while l1 and l2: if l1.val l2.val: cur.next l1 l1 l1.next else: cur.next l2 l2 l2.next cur cur.next # 接上剩余部分 cur.next l1 if l1 else l2 return dummy.next这里虚拟头节点是关键能避免很多“第一个节点到底取谁”的判断逻辑。如果不用dummy代码里就得单独处理l1和l2前面谁更小的问题不仅代码变长还容易在边界上出错。递归写法也能实现而且代码更短def merge_two_lists(l1, l2): if not l1: return l2 if not l2: return l1 if l1.val l2.val: l1.next merge_two_lists(l1.next, l2) return l1 else: l2.next merge_two_lists(l1, l2.next) return l2但递归的问题在于当链表非常长时会有栈溢出的风险笔试中虽然不太可能给你一个十万级的链表但面试官如果追问“递归的缺点是什么”你得答上来。我当时是把两种写法都写了一遍然后跟面试官分析了各自的适用场景这种交流比闷头写代码更容易加分。3.2 数组去重与热度排序Hash与排序的结合还有一道题是关于“车辆热度排名”的题干大意是“给定一组车辆ID和对应的热度值请按热度值从高到低输出车辆ID热度相同时按ID从小到大排列。”这题的难点并不是排序本身而是如何处理“热度值可能相同”的平局情况。用Python写的话sorted函数配合lambda可以轻松搞定car_list [ {id: 1001, heat: 95}, {id: 1002, heat: 88}, {id: 1003, heat: 95}, ] result sorted(car_list, keylambda x: (-x[heat], x[id]))但笔试往往不允许你用那么高级的写法他们更希望你实现一个稳定的排序算法或者至少能用语言自带的排序工具完成规则表达。我在刷题时习惯用一个技巧把“复合排序键”打包成元组利用Python元组字典序比较的特性能少写很多比较器代码。这道题还经常跟“数组去重”连在一起考。比如“输入一堆车辆ID可能出现重复请去重后按首次出现的顺序输出”。这题的通用解法是用一个HashSet记录已出现元素一次遍历完成去重def deduplicate(ids): seen set() result [] for car_id in ids: if car_id not in seen: seen.add(car_id) result.append(car_id) return result注意这里不能直接用set(ids)然后转list因为set是无序的会丢掉“首次出现的顺序”这个信息。做题时一定要看清题目要求的是“保持原顺序”还是“排序后输出”方向反了就是零分。3.3 链表反转的迭代与递归思路链表反转在很多公司的笔试里都是“必考题”瓜子二手车2019年也考了。题目描述很朴素“给定一个链表将其完全反转。”迭代解法很直接维护前驱、当前、后继三个指针逐个翻转指针方向。这里有个细节很多人会忘记在翻转前先保存后继节点导致链表断掉。def reverse_list(head): prev None cur head while cur: next_node cur.next # 先保存后继 cur.next prev # 翻转指针 prev cur # 移动前驱 cur next_node # 移动当前 return prev递归解法相对难理解一些但代码可以非常优雅。递归的核心是将head之后的子链表全部反转再把head拼接到尾部。def reverse_list_recursive(head): if not head or not head.next: return head new_head reverse_list_recursive(head.next) head.next.next head head.next None return new_head这道题的变体经常是“反转链表的第m到n个节点”或者“每K个节点一组反转”。如果基础版已经练熟了建议把这两个变体也看一下考试中遇到的概率相当高。4. 动态规划与状态转移拉开分差的压轴题4.1 买卖股票的最佳时机从一次交易到多次交易2019年秋招编程题中动态规划是区分度最高的一类题目。瓜子二手车考了“买卖股票的最佳时机”这个经典模型题目大意是“给出一段时间内某车型的价格变化数组只能买卖一次求最大利润。”这题最直观的解法是暴力枚举两层循环找最大差值时间复杂度O(n^2)但一般会超时。正确做法是动态规划思路记录到目前为止的最低价格每次更新最大收益。def max_profit(prices): min_price float(inf) max_profit 0 for price in prices: min_price min(min_price, price) # 今天之前的最低买入价 max_profit max(max_profit, price - min_price) # 今天卖出能赚多少 return max_profit这道题的时间复杂度是O(n)空间复杂度O(1)是典型的“看起来简单但要想清楚状态转移”的题目。面试官如果追问“如果允许无限次交易呢”你就需要升级到另一个经典DP模型状态转移方程里考虑“持有”和“不持有”两种状态。def max_profit_multi(prices): if not prices: return 0 n len(prices) # dp[i][0] 表示第i天结束后不持有股票的最大利润 # dp[i][1] 表示第i天结束后持有股票的最大利润 dp [[0, 0] for _ in range(n)] dp[0][0] 0 dp[0][1] -prices[0] for i in range(1, n): dp[i][0] max(dp[i-1][0], dp[i-1][1] prices[i]) dp[i][1] max(dp[i-1][1], dp[i-1][0] - prices[i]) return dp[n-1][0]笔试时我一般先把“只买卖一次”的基础版写出来然后主动跟面试官说“如果允许多次交易我还可以用动态规划扩展”这样的主动输出会拉高印象分。4.2 最长回文子串与二维DP另一道在秋招中出现频率较高的DP题是“最长回文子串”题干大意是“给定一个字符串找出其中最长的回文子串”。二维DP的解法是定义dp[i][j]表示子串s[i:j]是否为回文串状态转移时只需要看s[i]是否等于s[j]并且dp[i1][j-1]是否为回文。这里要注意遍历顺序必须保证在计算dp[i][j]时dp[i1][j-1]已经计算出来所以外层循环要从字符串末尾往前遍历。def longest_palindrome(s: str) - str: n len(s) if n 2: return s dp [[False] * n for _ in range(n)] start, max_len 0, 1 for i in range(n): dp[i][i] True for i in range(n - 1, -1, -1): for j in range(i 1, n): if s[i] s[j]: if j - i 1: dp[i][j] True else: dp[i][j] dp[i 1][j - 1] if dp[i][j] and j - i 1 max_len: start, max_len i, j - i 1 return s[start:start max_len]这个二维DP模型很有代表性之后遇到“最长回文子序列”、“编辑距离”这类题都是同一个套路。如果时间紧张至少要把这个模板手写三遍以上确保不卡壳。4.3 经典背包问题的签到变体还有一些题目就是经典背包问题的简易变体题干大意可能是“挑选若干辆车放入预算为M的购物车每辆车有价格和价值求不超过预算前提下的最大总价值。”本质上就是0-1背包问题。一个比较常见的错误是直接用二维数组做DP然后忘记压缩空间导致内存超限。其实0-1背包可以压缩成一维数组只需要从后往前遍历容量即可原因在于每个物品只能用一次从后往前可以保证当前物品不会被重复使用。def knap_sack(weights, values, capacity): n len(weights) dp [0] * (capacity 1) for i in range(n): for w in range(capacity, weights[i] - 1, -1): dp[w] max(dp[w], dp[w - weights[i]] values[i]) return dp[capacity]我刷题时习惯先写出二维版验证思路再改成滚动数组优化空间两种写法都要会因为面试官经常会把空间优化作为追问点。5. 笔试作答的策略与踩坑实录5.1 时间分配90分钟3道题的节奏根据2019年的实际情况看瓜子二手车的线上笔试一般给90分钟题目数量在3道左右。我的建议是前30分钟做掉最熟悉的题不要在一道题上死磕超过20分钟。编程题是踩点给分如果一道题卡住了先写下暴力解法的思路和部分代码即使不能AC也能拿一些用例分。一个比较实用的策略是先扫一遍全部题目按照“有思路的简单题 → 有思路的中等题 → 没思路的题”这个顺序来作答。不要被题目顺序迷惑有些公司会把难题放在前面就是为了让你打乱节奏。5.2 我见过的典型翻车现场很多人在字符串题上翻车是因为没有仔细处理输入格式。比如题目说“输入包含多个测试用例以空行分隔”有人就忘了处理空行直接导致读取数据错位。还有人在使用Python的input()读取含有前置空格的字符串时没有strip就输出了对比结果始终不一致。链表题翻车的高频原因则是指针操作顺序问题。很多人在写“反转链表”时忘记了保存下一个节点导致链表断链后陷入死循环。建议在草稿纸上先把指针变化的顺序画出来再动手写代码不要边写边想。动态规划题翻车的最常见原因是状态定义不清楚。如果状态定义错了后面的转移方程怎么推都是错的。我后来养成一个习惯开写前先在注释里写下dp[i]的含义以及当前状态有哪些选项然后再写循环。这个方法帮我规避了大量低级错误。5.3 本地调试技巧与提交前检查笔试环境通常没有完整的IDE很多公司用的在线编辑器连断点调试都不支持。我的习惯是先在本地IDE写好并用几组自测用例验证确认无误后再粘贴到在线答题区这样可以有效避免在线编辑器的自动缩进问题。提交前至少检查三样东西变量名有没有拼写错误、是否处理了空数组和单元素数组、是否遗漏了返回值。有时候一个简单的return缺失就能让整道题零分。我还习惯在代码里加上几行注释说明思路和关键状态因为有些公司的编程题是人工复核的清晰的注释和变量名能帮助面试官快速理解你的代码甚至在你代码有小bug时酌情给分。5.4 面试现场追问环节这种题目不只是在笔试里出现有些面试官会把编程题摘出来改为现场写代码然后追问你一连串细节。比如“如果数据规模大十倍你的解法还成立吗”“如果改成多线程并发请求该怎么设计”我个人体会是编程题答得好只是入场券现场追问才是真正拉开差距的地方。所以刷题不能只背答案要思考每个解法的时间和空间复杂度、能优化的点、以及如果数据规模变化该怎么调整。把这些问题想明白了笔试面试都能占到很大优势。6. 最后再分享一个小技巧我刷这套题最大的收获不是某一道题的解法而是学会了一个复盘的框架每道题做完后问自己三个问题——这道题考的是哪个数据结构或算法模型有没有遇到过同类型的题它们的差异在哪里如果把某个边界条件改掉解法还成立吗我之前刷题经常只是“做完了事”刷了三百多道还是感觉笔试不顺手。后来按照这个框架逐步复盘效果好了很多。现在看到任何一道题我第一反应不再是慌张地套模板而是先识别题目类型再根据边界条件决定具体解法。这套瓜子二手车2019秋招编程题难度不算高但覆盖的模型很典型当作全类型题目练习材料非常合适。建议你每道题都亲手写一遍写完再想一想我上面说的三个问题。把这一批题吃透了相信你去面试其他互联网公司的底气也会足很多。