
先说明一句我自己刷这批题目已经是几年前的事了但直到现在搜狗这批后端笔试题依然是很多准备校招的人绕不开的参考资料。原因倒不是题目本身有多难而是它的出题风格非常典型不玩偏题怪题考察的全是一个后端工程师日常开发中真正会用到的基础能力比如字符串处理、数据结构选择、边界条件控制、性能优化意识。这篇文章我想用复盘的角度把搜狗2019秋招后端第一场编程题的考察逻辑、解题思路和我在实际做题过程中的一些经验整理出来希望能给正在准备后端校招的同学一些参考。搜狗2019后端笔试编程题复盘题目拆解、解题思路与避坑指南1. 搜狗后端笔试的整体观察1.1 笔试定位与考察范围搜狗的后端岗笔试向来以“基础扎实、代码落地”著称不会出那种让人看完题目就懵掉的超长场景题也不会故意在题目描述里挖陷阱。2019秋招第一场这套题整体风格可以用两个词概括务实、细。务实体现在考察内容上题目基本围绕字符串、数组、数学推导、排序搜索这几大类展开这些都是最贴近后端日常开发的内容。细体现在对边界条件的考察上比如空串处理、大数越界、递归深度限制等这些恰恰是很多同学在刷LeetCode时容易忽略、但在实际生产代码中必须考虑的问题。从岗位匹配度来看搜狗作为搜索和输入法起家的公司后端业务对字符串处理、文本匹配、大数据量下的性能优化要求很高所以笔试题目中字符串相关题型占比明显偏重这不是巧合而是业务需求在人才筛选上的直接投射。准备搜狗笔试字符串处理相关的题目一定要重点练习。1.2 题量与时间分配策略搜狗秋招笔试一般是2到3道编程题时间通常在90分钟左右。注意这个时间不是让你慢慢悠悠写代码的而是要留出至少20到30分钟的缓冲时间用来调试。很多同学在笔试时容易犯一个错误拿到第一题就埋头狂写结果写到一半发现自己思路有问题或者被某个边界条件卡住导致后面的大题时间不够。我的建议是拿到题目后先用5分钟把三道题全部扫一遍快速判断每道题的难度和自己熟悉度然后从最稳的题目开始做。先把确定能拿下的分数拿到手再去攻那些需要动脑子的题。另外搜狗的笔试环境一般支持本地IDE允许使用自己熟悉的语言。从实际经验看C和Java是主流选择也有不少人用Python和Go。我不建议临时换语言就用你平时刷题最熟练的语言笔试现场不是试新语言的地方。2. 核心题型解析与通用解题思路2.1 字符串处理笔试中的重头戏字符串题在后端笔试里出现频率极高因为字符串处理能力直接反映了一个工程师对内存操作、复杂度控制和边界条件把控的基本功。搜狗这批题里字符串相关的题目占了相当大的比重常见的有字符串压缩、子串匹配、字符统计排序、简单正则匹配等。以字符串压缩这类题为例核心思路是遍历一次字符串统计连续相同字符的个数然后输出“字符个数”的压缩形式。这类题看起来简单但有几个坑是必踩的第一压缩后的字符串可能比原串更长。比如abc压缩后是a1b1c1长度不变甚至更长这时候题目可能要求返回原串。这个条件在题目描述里一般会写但很多人做题太快会漏看。第二数字转字符串的处理。如果用的是C切记要处理整数转字符串直接用to_string或者手写转换都可以。如果用的是Java注意StringBuilder的高效使用不要在循环里用String 做拼接。第三内存和性能考量。字符串题一般要求 O(n) 的时间复杂度空间上最好控制在 O(1) 或 O(n) 常数级。写代码的时候要下意识检查自己的解法是否有不必要的substring操作这些操作一次就是 O(n)。我给一段示例代码用C实现一个带原串长度判断的字符串压缩#include string #include iostream std::string compressString(const std::string str) { if (str.empty()) return str; std::string result; int count 1; for (int i 1; i str.size(); i) { if (i str.size() str[i] str[i - 1]) { count; } else { result.push_back(str[i - 1]); result std::to_string(count); count 1; } } return result.size() str.size() ? result : str; }注意在循环里我用了i str.size()的处理方式这样可以在循环内部统一处理最后一组相同字符避免循环结束后再补一次处理逻辑。这种细节在笔试时很加印象分但更重要的是它能减少逻辑分支降低出错概率。2.2 数组与数学推导题找规律比暴力更重要后端笔试中还有一类高频题型是数组操作和数学推导这类题往往表面看起来是模拟实际考察的是数学归纳能力。搜狗这批题里有一个很典型的场景给定一个数组要求找出满足某种条件的最值或数量如果直接暴力枚举时间复杂度通常会是 O(n²) 甚至更高在数据量大的时候必然超时。这类题的通解思路是“先排序再双指针”或者“先哈希再遍历”。比如求数组中两数之和等于目标值的组合暴力解法是两层循环时间复杂度 O(n²)用哈希表存储已经遍历过的元素一趟遍历就能解决时间复杂度降到 O(n)。而如果题目要求的是三数之和那就先排序再固定一个数用双指针在剩余区间里查找。我曾经被一道题卡了很久题目大意是给一个数组求连续子数组的最大和。如果不知道动态规划的思路很容易写出三层循环的暴力解法。其实这就是经典的Kadane算法遍历数组维护当前子数组和如果当前子数组和变成负数就丢弃重新开始。一个状态转移方程就解决了时间复杂度 O(n)空间复杂度 O(1)。#include vector #include algorithm int maxSubArray(const std::vectorint nums) { if (nums.empty()) return 0; int currentSum nums[0]; int maxSum nums[0]; for (size_t i 1; i nums.size(); i) { currentSum std::max(nums[i], currentSum nums[i]); maxSum std::max(maxSum, currentSum); } return maxSum; }这类题其实考察的是你有没有“算法优化”的意识。后端工程师每天面对的都是海量数据如果你写的代码在 O(n²) 级别放到生产环境就是事故。所以刷题不只是为了过笔试更是为了训练自己的复杂度敏感度。2.3 排序与查找手写基础算法仍是基本功虽然现代开发语言都有现成的排序函数比如C的std::sort、Java的Arrays.sort、Go的sort.Slice但笔试中仍然可能会考察手写排序算法。原因很简单面试官想看你是否理解排序的底层原理而不只是会调包。搜狗这批题中涉及排序的主要是要求按某种自定义规则排序。常见的有按字符串长度排序、按频率排序、按结构体某个字段排序。这类题一般不要求你手写快排但要求你会用语言提供的排序函数并且能写出正确的比较器Comparator。这里有一个特别容易被坑的点比较器的写法。C中比较器返回true表示第一个参数排在前面Java中compare方法返回负数表示第一个参数排在前面Python中key函数返回的值作为排序依据Go中Less返回true表示i排在j前面。这些语法细节平时不写很容易忘记笔试前一定要过一遍自己熟悉语言的排序API。另一个高频考点是二分查找。二分查找看起来简单但写对边界条件非常考功夫。我用的是左闭右开区间写法这样可以统一处理各种边界条件减少死循环的概率#include vector int binarySearch(const std::vectorint nums, int target) { int left 0, right nums.size(); while (left right) { int mid left (right - left) / 2; if (nums[mid] target) { return mid; } else if (nums[mid] target) { left mid 1; } else { right mid; } } return -1; }注意mid left (right - left) / 2而不是(left right) / 2这样可以防止left right整数溢出。这个细节在后端开发中同样重要很多线上问题的根源就是整数溢出。2.4 动态规划与状态压缩进阶题的常客搜狗后端笔试的编程题中动态规划题目难度通常处于中等偏上。常见的有背包问题变种、最长公共子序列、编辑距离、打家劫舍系列等。这一块的解题思路比较固定明确状态定义推导状态转移方程初始化边界条件最终求解。我做动态规划题的经验是先画出状态转移表把整个二维数组填一遍理解状态之间的依赖关系然后再考虑能不能用滚动数组降低空间复杂度。以最长公共子序列LCS为例#include string #include vector #include algorithm int longestCommonSubsequence(const std::string text1, const std::string text2) { int m text1.size(), n text2.size(); std::vectorstd::vectorint dp(m 1, std::vectorint(n 1, 0)); for (int i 1; i m; i) { for (int j 1; j n; j) { if (text1[i - 1] text2[j - 1]) { dp[i][j] dp[i - 1][j - 1] 1; } else { dp[i][j] std::max(dp[i - 1][j], dp[i][j - 1]); } } } return dp[m][n]; }这里有个小经验C中vectorvectorint的初始化方式可以一次性将整个二维数组赋值为0比循环赋值更简洁。但这种写法在数据量较大时有性能隐患笔试中可控的数据范围内没有问题。3. 一道典型综合题的完整拆解3.1 题目示例字符串去重排序这里我构造一道典型的搜狗风格题目来完整演示解题过程。这类题目综合了字符串处理、哈希统计、排序等基础能力在搜狗笔试中很常见。题目描述给定一个字符串请将其中出现的字符按照出现次数从高到低排序如果出现次数相同则按照字符的ASCII码从小到大排序。输出排序后的字符串每个字符只输出一次。例如输入statistics输出stiac。这道题表面看起来是字符串题实际考察的是三个能力点字符频率统计、自定义排序、字符串拼接。我用Java来实现因为Java在字符处理和排序API上最直观import java.util.*; public class CharFrequencySort { public static String frequencySort(String s) { if (s null || s.isEmpty()) return ; // step 1: 统计字符频率 MapCharacter, Integer freq new HashMap(); for (char c : s.toCharArray()) { freq.put(c, freq.getOrDefault(c, 0) 1); } // step 2: 将字符放入list ListCharacter chars new ArrayList(freq.keySet()); // step 3: 自定义排序 chars.sort((a, b) - { if (!freq.get(a).equals(freq.get(b))) { return freq.get(b) - freq.get(a); // 频率降序 } return a - b; // ASCII升序 }); // step 4: 拼接结果 StringBuilder sb new StringBuilder(); for (char c : chars) { sb.append(c); } return sb.toString(); } public static void main(String[] args) { System.out.println(frequencySort(statistics)); // 输出 stiac } }3.2 从暴力到优化一个完整的思考过程上面这道题在笔试现场你可能会经历这样一个思考过程。第一时间想到的暴力解法是遍历字符串用二维数组记录每个字符的出现次数然后对每个不同字符进行一次循环比较排序。这里的问题在于排序部分如果手写选择排序复杂度是 O(n²)n是不同字符的数量最多也就256个所以在数据量上这种解法也不会超时。但作为笔试答卷代码质量和思路清晰度同样重要。我上面给出的解法的核心优化点是第一用HashMap代替数组做频率统计。对于可能包含Unicode字符的字符串HashMap更通用不局限于ASCII码范围。第二getOrDefault方法一行代码就完成了“获取并更新”的操作比先判断containsKey再操作要简洁得多而且不会引入额外的分支错误。第三用ListCharacter存储字符集合通过freq.get(key)动态获取排序字段。这种做法的好处是排序字段和字符本身解耦以后如果题目改成“按字符最后出现位置排序”只需要修改比较器逻辑不需要改动整体结构。第四StringBuilder拼接是 Java 处理字符串拼接的标准方式直接用String 在字符串不长时性能差别不大但养成好习惯非常重要。笔试的时候我会在代码注释里用中文简单标注每一步的意图。搜狗笔试环境支持代码运行和多次提交不需要一次性写出完美代码可以反复调试但注释清晰能帮你更快定位逻辑错误。3.3 常见错误与边界条件这类字符串统计排序题有几个高频错误点我看到很多人犯过没有处理空字符串输入。如果输入为空字符串直接返回空字符串即可而不应该返回null或者报空指针异常。忽略字符频率相等时的排序规则。题目要求频率相等时按ASCII码升序但很多人只写了频率降序导致结果不符合预期。使用TreeMap替代HashMap做排序。这里有一个隐含逻辑问题TreeMap默认按key排序但题目要求按value排序所以必须先统计完再排序不能直接用TreeMap。我在实际笔试中写完代码后习惯性做三件事用空字符串跑一遍、用单字符字符串跑一遍、用全是相同字符的字符串跑一遍。这三组用例几乎能覆盖所有的边界分支快速定位大部分问题。4. 笔试现场的踩坑记录与提效技巧4.1 时间分配的实战经验我在搜狗笔试现场踩过最大的坑是时间分配失误。当时我拿到题目后看到第一题比较熟悉就花了大量时间精雕细琢把代码写得非常规范漂亮但没想到后面有一道动态规划题需要很长时间推导最后只能匆匆写个半成品交上去。后来我总结了一套通用时间分配策略前5分钟通读所有题目判断难度确定答题顺序优先做自己最熟悉的题型先拿保底分每道题最多分配25分钟左右超过时间果断止损去做下一题留至少10分钟做整体检查重点检查边界条件和编译是否通过这套策略的核心思路是笔试题目的分值往往和难度不成正比简单题的分值可能和难题差不多但简单题花的时间少、正确率高。在有限时间内先把简单题全部拿到满分比死磕一道难题的性价比高得多。4.2 本地环境和在线评测的差异搜狗笔试的在线评测系统和本地IDE有几个关键差异不提前适应很容易吃亏。第一输入输出的格式要求。本地IDE你可以随意打印调试信息但在线评测系统要求你的输出必须严格匹配题目要求多一个空格、少一个换行都会被判错。我的习惯是在本地用标准输入重定向来模拟评测环境# 本地模拟在线评测的方式 ./my_program input.txt output.txt diff output.txt expected.txt这能提前发现输出格式问题避免交上去之后才被提示“格式错误”。第二数据规模对算法复杂度的限制。在线评测系统有严格的时间限制通常在1到2秒之间。如果你的算法是 O(n²) 而 n 达到了10^5级别几乎是必然超时。做题时可以根据数据规模快速估算复杂度是否可行n 10^6O(n) 算法没问题n 10^4O(n²) 算法勉强可以n 10^3O(n³) 算法可以尝试n 10^2O(2^n) 也可能过这个估算能力不是天生的我在刷题时经常刻意练习看到题目先猜复杂度要求再决定解题思路。4.3 常见编译错误速查表笔试现场时间宝贵编译错误是最浪费时间的问题。我整理了一张平时刷题时经常遇到的编译错误对照表错误类型常见原因快速解法数组越界循环边界写错检查 for 条件里的和空指针异常未判断输入为空函数开头加空值判断整数溢出int 类型范围不够改用 long 或 long long栈溢出递归深度过大改用迭代或显式栈未初始化变量局部变量未赋值声明时立即赋初值比较器写反降序升序混淆用两个具体例子验证StringBuilder 忘导包Java 导包遗漏检查 import 列表这张表我贴在显示器旁边很久每次笔试前都会扫一眼。别小看这些基础问题很多人在紧张的考场环境中恰恰是在这种低级的编译错误上浪费了大量时间。5. 如何系统准备搜狗后端笔试5.1 刷题路线的三个阶段如果你离笔试还有两到三周我建议把刷题分为三个阶段每个阶段目标明确效率远高于漫无目的地刷题。第一阶段第1周基础题型全覆盖。按字符串、数组、链表、栈队列、二叉树、排序搜索、动态规划这七个大类每天专攻一个类别。每个类别刷够10道题先求数量再求质量把各种常见解法套路都过一遍。第二阶段第2周真题和模拟题实战。找搜狗历年的笔试真题以及牛客网上其他大厂类似风格的后端真题按真实考试环境模拟答题。关键是限时完成训练时间分配能力。第三阶段第3周查漏补缺和手速训练。把之前做错的题重新刷一遍重点分析错误原因。每天保持至少3道题的训练量保持手感和状态。5.2 语言选择与核心API速查搜狗后端笔试C、Java、Python、Go都能用但选择哪门语言需要策略性考量。我用C写算法题最多原因是STL库非常丰富容器和算法函数完备性能也好。但C的缺点是语法细节多如果平时不经常写笔试时容易在指针和内存管理上浪费调试时间。Java的优势是生态成熟API丰富Collections工具类提供了大量的排序、查找函数写起来很顺手。缺点是代码相对冗长刷题速度可能略慢。Python写起来最快适合刷题但性能在极端数据量下可能吃亏而且在线评测环境中Python的递归深度限制比较严格。我的建议是如果你还在准备阶段优先选C或Java因为它们在后端工程中的应用更广泛笔试风格也更契合。如果你只是临时突击用你最熟练的语言就好不要临阵换枪。我整理了一份常用语言的核心API清单放在这里供大家参考C关键APIstd::stringsubstr、find、find_first_of、append、to_stringstd::vectorpush_back、emplace_back、erase、resizestd::map/std::unordered_mapinsert、find、countalgorithmsort、reverse、unique、binary_search、lower_boundJava关键APIStringsubstring、indexOf、charAt、toCharArrayArrayListadd、remove、containsHashMap/TreeMapput、get、getOrDefault、keySetArrayssort、binarySearch、fillCollectionsreverse、max、minGo关键APIstringsSplit、Join、Contains、IndexsortSlice、Searchcontainer/list双层链表实现笔试前花半天时间把这些API过一遍比临时翻文档效率高得多。5.3 复盘方法论从做对到做透刷题最忌讳的就是做对了就扔到一边也不管有没有更优解法。我见过很多同学刷了500题还是笔试挂原因就是陷入了“刷题数量陷阱”只看数量不看质量。我的复盘方法论是“三遍做题法”第一遍独立思考能写出多少算多少不限时但要求尽量完整。第二遍看别人的题解重点看有没有比自己的解法更好的思路并重新实现一遍。第三遍隔一周之后重新做这道题要求一次通过代码质量和思路都要达到自己满意。这套方法的核心逻辑是第一遍暴露问题第二遍学习答案第三遍检验掌握。很多题目做一遍容易忘做到第三遍才算真正内化为自己的能力。另外我强烈建议维护一个错题本。不是简单的抄题而是记录错误原因、错误类型和对应的解法套路。每次笔试前翻一遍错题本比重新刷题效率高得多。5.4 搜狗笔试之外的补充建议搜狗后端岗的面试流程中笔试只是第一关。过了笔试之后还会有技术面、HR面等环节。但我见过不少人过于重视笔试忽视了简历和项目经历的准备。技术面的时候面试官会深挖项目里的技术难点如果你准备不充分笔试分再高也没用。从我个人的经验来看搜狗更看重的是候选人的逻辑思维能力和基础功底。笔试题目往往只是用来筛选基础能力明显不足的候选人真正的分水岭在面试环节。所以在准备笔试的同时不要忘了同步准备项目和基础知识的系统性复习。我建议在刷题之余每周抽点时间复习计算机网络、操作系统、数据库、分布式系统等后端核心知识。这些内容虽然不在笔试中直接考但在面试中几乎必考。笔试和面试的准备应该同步推进而不是等笔试过了再去准备面试。最后再分享一个小经验笔试前一天的晚上不要再做新题了。把错题本翻一遍把常用API过一遍早点休息。笔试考的不只是知识储备还有状态和心态。我见过太多人因为考试前一天熬夜刷题结果第二天考试时头脑发昏连简单的边界条件都想不到。保持好状态平时训练到位了笔试结果自然水到渠成。