尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

搜狗测试工程师秋招编程题解析:从边界条件到工程思维

搜狗测试工程师秋招编程题解析:从边界条件到工程思维 搜狗2019秋招测试工程师编程题合集第二场我最近翻出来重新刷了一遍说实话这套题比很多大厂算法岗的题目要“亲民”得多但恰恰是这种看似基础的题最能筛掉一批人。搜狗作为做搜索、输入法、浏览器起家的公司测试工程师岗位的笔试编程题从来不是靠深奥算法压人而是靠基础功底、边界条件和工程习惯来考察候选人。如果你正在准备测试开发岗、测试工程师岗的秋招或者跳槽面试这套题和它背后的考法值得认真吃透。这篇文章我不会只贴题目和答案而是会从“搜狗为什么这么出题”这个角度切入把每一类题型的破题思路、边界处理、测试思维、实战流程全部拆开讲透。内容适合三类人一是准备测试岗和测试开发岗笔试的应届生二是想转行做测试、需要补算法基础的同学三是已经在职、想系统整理自己基本功的测试工程师。哪怕你手头已经没有2019年的原题卷这篇文章里的题型拆解和练习方法放到今天的面试里一样能用。1. 搜狗这套编程题考的是什么1.1 面试流程里编程题的位置和作用搜狗当年的秋招流程和其他互联网公司差不多简历初筛过了以后先做一轮在线笔试笔试通过才进入技术面试。笔试一般分为两部分一部分是客观题考察计算机网络、操作系统、数据结构这些计算机基础另一部分就是编程题需要在限定时间内在线提交代码系统自动判题。别小看这编程题环节它其实是整个流程中淘汰率最高的一关。原因很简单客观题大家还能蒙一蒙编程题会就是会、不会就是不会代码一跑就知道结果。尤其搜狗这类以搜索和输入法为核心业务的公司质量保障团队非常看重候选人写代码的严谨程度——搜索结果的排序、输入法的词库匹配、浏览器内核的兼容性这些场景下任何一个小边界条件没处理好线上就会出现事故。所以笔试编程题本质上不是考你会不会“算法竞赛”而是考你有没有写“靠谱工程代码”的潜质。第二场合集共三道题难度梯度比较明显。第一题基本是送分题考察最基本的数组遍历和条件判断第二题开始涉及数据结构比如栈、哈希表、双指针第三题则是综合题需要结合多种技巧才能拿到高分。这个安排其实很典型先让你拿保底分再逐步筛选出真正有算法思维的人。1.2 三道题背后共通的考察点如果只盯着题目本身去刷很容易忽略一个关键事实搜狗测试工程师的编程题重点从来不只是“算法复杂度”这一个维度。按我这些年做测试和面试官的经验这类笔试主要考察四个能力维度第一个是数据结构基础。数组、字符串、链表、栈、队列、哈希表这些基础结构必须熟练到肌肉记忆。搜狗的题不会直接告诉你“这道题用栈”而是把场景藏在问题描述里你得自己识别出来。第二个是边界思维。测试工程师写代码和开发工程师有个很大的区别测试更习惯“找茬”。空数组、只有一个元素、元素全部相同、数值取到最大值、输入超长……这些边界条件在开发眼里可能觉得“没必要”但测试出身的人会天然地先把这些情况列出来。搜狗的判题系统里边界测试用例往往占了一半以上。第三个是复杂度意识。第一题的暴力解可能能过但第二题第三题如果不做优化大概率超时。你得能快速估算自己的解法在最坏情况下的时间复杂度和空间复杂度并作出合理取舍。第四个是代码风格。在线笔试不要求你写出生产级代码但变量命名清晰、逻辑结构分明、必要的地方写注释这些习惯面试官在后台是能看到的也会影响后续面试的评价。1.3 时间分配与答题顺序策略在线笔试的时间一般是一个半小时到两个小时三道题里第一题简单、第二题中等、第三题偏难。我见过太多人栽在时间分配上上来就盯着第三题猛干结果卡了四十分钟没做出来回头发现第一题还没写。正确的策略是“稳拿一、二攻坚三”。先把第一题快速通过保证有分入账然后花主要精力在第二题上尽量拿满分第三题如果思路清晰就写写不出来也要把暴力解法写上并注释说明思路部分用例也能得分。判题系统通常是按通过的测试用例数量给分不是一票否决所以“写了比不写好暴力比空白好”这句话一定要记住。另外注意一点在线笔试的编程环境不像本地IDE那么舒服没有自动补全调试也要靠打印日志。平时练习时尽量用平台自带的编辑器做题不要过度依赖IDE的语法提示否则考场上会非常难受。2. 编程题分类解析三类高频题型的破题思路2.1 数组操作类双指针和原地修改是核心搜狗这套题里数组类题目出镜率极高而且通常不是单纯让你遍历一遍就完事而是要你“原地”操作或者要求时间复杂度控制在 O(n)。举一个和当年考题风格非常接近的例子给定一个有序数组原地删除重复出现的元素使每个元素只出现一次返回删除后数组的新长度。不要使用额外的数组空间必须原地修改输入数组。拿到这个题第一反应可能是“遇到重复就把后面的元素往前挪”这确实能做但每次删除都涉及后续元素的大规模移动最坏时间复杂度是 O(n²)数据量一大必然超时。正解是双指针。慢指针 i 指向“已处理区域的最后一个位置”快指针 j 从头往后扫描。每次发现 nums[j] ! nums[i] 时就说明遇到了新元素把 nums[j] 复制到 nums[i1]然后 i 前进一位。这样一趟扫描就完成去重时间复杂度 O(n)空间复杂度 O(1)。def remove_duplicates(nums): if not nums: return 0 i 0 for j in range(1, len(nums)): if nums[j] ! nums[i]: i 1 nums[i] nums[j] return i 1这个题的核心难点不是算法本身而是边界条件。写代码之前先在脑子里过一遍数组为空怎么办数组只有一个元素怎么办所有元素都相同怎么办全部不同怎么办把这些情况都测过一遍代码才算真正写完。测试工程师做笔试题的时候这种“用例先行”的思维是最加分的。数组类题目还有一个常见变体是“多数元素”也就是找出数组中出现次数超过一半的数。常规做法是用哈希表计数空间复杂度 O(n)。但如果题目要求空间 O(1)就得用摩尔投票法维护一个候选值和计数器遍历时相同则计数加一不同则减一计数归零就更换候选值。这个方法原理很巧妙——超过一半的数在“抵消”过程中一定会剩到最后。2.2 字符串处理类栈和哈希表是左膀右臂字符串类题目在测试岗笔试里出现频率也非常高因为搜狗的核心产品——输入法和搜索——每天都在处理海量字符串数据。字符串题表面看五花八门但底层套路相对固定括号匹配用栈字符计数用哈希表子串查找用滑动窗口。拿“括号匹配”这道经典题来说给定一个只包含(、)、{、}、[、]的字符串判断字符串是否有效。有效字符串需要满足左括号必须用相同类型的右括号闭合左括号必须以正确的顺序闭合。这道题就是个典型的栈应用。遍历字符串遇到左括号就入栈遇到右括号就检查栈顶是不是匹配的左括号是则弹出继续否则直接返回 false。最后栈为空才说明全部匹配成功。def is_valid(s): stack [] mapping {): (, }: {, ]: [} for ch in s: if ch in mapping: if not stack or stack[-1] ! mapping[ch]: return False stack.pop() else: stack.append(ch) return not stack这里有个特别容易踩的坑很多初学者只记得用栈却忘了判断栈是否为空就直接取栈顶导致运行时错误。正确做法是在弹出之前先检查stack是否为空为空说明当前右括号没有对应的左括号直接判定无效。字符串题的另一大类是“计数类”比如“字符串中第一个只出现一次的字符”。这类题用哈希表记录每个字符出现的次数然后第二次遍历字符串找到第一个次数为1的字符。需要注意的是如果字符串长度非常长比如几十万字符要选择一个合适的字符集遍历范围避免做了无用的循环。2.3 算法思维类双指针和二分查找的边界陷阱第三类题型是算法思维题难度通常会高一个档次。这类题很少直接考“快排怎么写”而是考你对某个算法技巧的理解深度最常见的就是双指针和二分查找。一个是“有序数组的两数之和”题目给定一个已按升序排列的整数数组和一个目标值找出数组中和为目标值的两个数返回下标。经典解法是首尾各放一个指针如果两数之和大于目标值说明尾部指针指向的数太大尾部指针左移如果小于目标值说明头部指针指向的数太小头部指针右移相等则返回。def two_sum(nums, target): left, right 0, len(nums) - 1 while left right: cur_sum nums[left] nums[right] if cur_sum target: return [left, right] elif cur_sum target: left 1 else: right - 1 return []为什么双指针在有序数组上一定正确关键在“单调性”左指针向右移动时两数之和只会增大或不变右指针向左移动时两数之和只会减小或不变。所以每次比较后都能排除一部分不可能的解不会漏掉正确答案。这类题的易错点在于处理“重复元素”。如果题目允许返回多个组合或者数组中存在重复值就需要在移动指针时跳过重复元素否则可能输出相同的组合。二分查找就更经典了。注意区间定义要统一左闭右闭[left, right]和左闭右开[left, right)是两种写法循环终止条件也不同。我个人的习惯是统一用左闭右闭这样while left right退出循环时left就是第一个大于目标值的位置。很多人写着写着把两种写法混淆导致死循环或漏解这个在笔试里是大忌。3. 测试思维在编程题里的隐形加分项3.1 边界条件测试工程师的看家本领我前面反复提到边界条件因为这是搜狗这类公司测试岗笔试中最鲜明的特色。同样一道算法题开发岗候选人只要能跑通常规用例就能拿分但测试岗候选人的代码会被更多“刁钻”用例检验——判题系统本身就是从测试视角设计的。那么考试的时候哪些边界值得专门留意我整理了一份高频边界清单写代码时逐条自查输入为空数组长度为0、字符串为空串。比如去重题里not nums直接返回0。输入长度为1很多循环逻辑在长度为1时会出问题尤其是双指针和滑动窗口。全相同元素去重后长度应该是1。全不同元素代码不应该有任何误删或误判。目标值不存在比如两数之和无解时要有明确的返回约定。数值极值数组元素可能取到最大整数或最小整数注意加减乘除是否会溢出。重复元素与相同值处理排序后相邻元素的处理、哈希表更新策略。超长输入时间复杂度和空间复杂度是否会爆掉。把这些边界情况在写代码之前就列出来其实是把“测试设计”的思维用在了写代码上。这是测试工程师天然的优势别浪费掉。3.2 自己设计测试用例从写代码到写用例笔试过程中很多人写完代码就提交然后等着系统判分。但一个合格的测试工程师应该在自己脑子里“跑”一遍用例。我刷题的时候有个习惯每个题目写完会在本地或者在线编辑器里手动构造至少五六个用例覆盖正常情况、边界情况和异常情况。以“删除有序数组重复项”为例我至少会构造这些用例用例编号输入数组期望输出1[]02[1]13[1,1,1,1]14[1,2,2,3,3,4]45[1,2,3,4,5]56[0,0,1,1,1,2,2,3,3,4]5跑完这些用例再提交。哪怕最后时间不够这套习惯也能帮你提前发现大量低级错误。不要觉得这是浪费时间恰恰是这种“测试先行”的思维方式才是测试工程师岗位面试官真正想从笔试题里看到的。3.3 对多组输入输出的处理在线笔试还有一个隐藏考点输入输出格式。搜狗这套题里有些题目的输入可能是多组测试数据而不是只有一组。很多同学在本地IDE里写习惯了单组输入一遇到多组就懵。比如题目描述“输入包含多组测试数据每组占一行”或者“输入第一行为案例数T接下来T行是每个案例的输入”这两种格式都必须熟练处理。第一种用循环读取import sys for line in sys.stdin: nums list(map(int, line.strip().split())) print(solution(nums))第二种先读T再循环import sys lines sys.stdin.read().strip().split() t int(lines[0]) idx 1 for _ in range(t): n int(lines[idx]); idx 1 nums list(map(int, lines[idx:idxn])); idx n print(solution(nums))这种细节看起来不起眼但在考试环境下一旦读入数据的方式写错后面所有逻辑全部白写而且很难定位问题。建议在考前专门用十分钟练一下各种输入输出模板做到手到擒来。4. 实战《合并两个有序数组》从读题到AC全流程4.1 题目描述与题意分析接下来我用搜狗这套题里很接近真题风格的一道题完整走一遍从读题到提交的全流程。题目如下给定两个有序整数数组 nums1 和 nums2将 nums2 合并到 nums1 中使 nums1 成为一个有序数组。其中 nums1 的长度为 mn前 m 个元素是待合并的元素后 n 个元素为0占位需要原地合并不返回新数组。先别急着写代码先读懂题目的关键约束。第一nums1 的长度是 mn也就是说它已经预留了 n 个空位第二要求原地合并不能用另一个临时数组来接结果。这两个约束直接决定了你算法的设计方向。4.2 从暴力到优化为什么逆向双指针最合适最直接的思路是把 nums2 整体拼到 nums1 末尾然后调用排序函数。这个做法在功能上没错但时间复杂度是 O((mn)log(mn))而且没有利用上“两个数组本身已经有序”这个条件明显不是出题人想要的答案。进一步思考可以再用一个辅助数组双指针依次比较两个数组头部元素取较小者放入辅助数组。时间复杂度 O(mn)空间复杂度 O(n)——但题目要求原地合并用辅助数组空间不满足要求所以还要继续优化。这里最关键的一步是把“从头开始比较”改成“从尾部开始比较”。两个数组的末尾元素中较大的一定是合并后数组的最后一个元素。于是我们可以在 nums1 的尾部也就是第 mn-1 个位置从后往前填充每次比较 nums1 当前末尾元素和 nums2 当前末尾元素较大的放到 nums1 的尾部空闲位置并移动对应的指针。这样既不需要额外数组又充分利用了两个数组有序的性质。def merge(nums1, m, nums2, n): p1 m - 1 p2 n - 1 p m n - 1 while p1 0 and p2 0: if nums1[p1] nums2[p2]: nums1[p] nums1[p1] p1 - 1 else: nums1[p] nums2[p2] p2 - 1 p - 1 while p2 0: nums1[p] nums2[p2] p2 - 1 p - 1这个写法的时间复杂度 O(mn)空间复杂度 O(1)已经是这道题的最优解法。代码里有个容易被忽略的点第一个 while 结束之后如果 nums2 还有剩余元素需要单独处理而 nums1 如果还有剩余元素则不需要处理因为它们本来就在正确的位置上。4.3 用例设计与自测提交前的最后一道关卡代码写完之后别急着提交先在建好的“测试用例集”里验证一遍。我针对这个合并题设计了以下用例用例编号nums1mnums2n合并后期望结果1[1,2,3,0,0,0]3[2,5,6]3[1,2,2,3,5,6]2[1,0,0]1[2,3]2[1,2,3]3[0,0,0]0[1,2,3]3[1,2,3]4[1,2,3,0,0,0]3[]0[1,2,3]5[3,4,5,0,0,0]3[1,2,4]3[1,2,3,4,4,5]6[1,1,1,0,0]3[1,1]2[1,1,1,1,1]用例1是常规情况用例2覆盖 nums1 为空尾部占位的场景用例3覆盖 nums1 初始为空的情况用例4覆盖 nums2 为空用例5覆盖两个数组大小交叉的情况用例6覆盖所有元素相等的情况。跑完这6个用例代码基本可以放心提交。这类“写代码前先列用例”的习惯我从面试一直带到了实际工作中。现在我和同事做代码评审也会刻意要求新人在讲解自己写的测试工具时先给出测试计划再讲实现。这个习惯在笔试里帮了我很大的忙也推荐你养成。5. 备战搜狗这类测试岗笔试的几条实在建议5.1 基础不牢怎么办三个月复习路线如果你现在离秋招还有三四个月但算法基础还比较薄弱不用慌按路线踏实走第一个月主攻数据结构基础。把数组、链表、栈、队列、哈希表、树、堆这七种结构的基本操作全部手写一遍做到不查资料也能写出无 bug 版本。手写链表反转、二叉树中序遍历、用数组实现栈和队列这些高频题要练到条件反射。第二个月主攻算法思维。分类刷题双指针、二分查找、滑动窗口、动态规划入门、回溯入门每类题至少刷15道不求多但求理解。刷完每道题在笔记里写清楚三件事题目是什么类型、核心思路是什么、边界条件有哪些。第三个月进入模拟考试节奏。每周找两三套历年测试岗笔试题严格按照考试时间做做完之后花时间复盘。复盘的重点不是“我为什么没做出来”而是“这道题属于哪种模式我下次遇到能不能快速识别”。5.2 笔试现场最容易翻车的五个细节第一死磕一题导致满盘皆输。如果一道题想了十五分钟完全没有思路果断跳过先做后面的题回头再补。第二不处理输入边界就开始写逻辑。很多题目的输入格式有坑比如相邻数字之间有多个空格、字符串中包含空白字符、数组元素可能为负数等等。读取数据时先把这些情况处理好。第三变量命名随意。在线笔试系统里写代码虽然不像实际协作那样严格要求命名规范但a、b、tmp这种命名会让你自己调试时都看不懂逻辑。用left、right、cur这种有语义的名字调试效率能提升一倍。第四忽略复杂度分析。写完代码以后养成在注释里写一句时间复杂度和空间复杂度的习惯。面试官看你的代码时能立刻了解你是否有算法意识而且这个习惯在后面的技术面试里也很有用。第五提交前不检查输出格式。在线判题通常对输出格式非常严格多一个空格、少一个换行都可能导致判错。如果平台支持本地调试先用题目给的样例跑一遍确认输出完全一致再提交。5.3 编程题之外测试岗面试还要准备什么笔试只是第一关真正拿到offer还要看技术面试表现。搜狗测试工程师的技术面试通常会围绕几个方向展开首先是项目经历尤其是你做过哪些测试相关的工作有实际项目最好没有的话自己用爬虫、接口自动化、性能测试工具做一个小项目也能加分其次是测试基础知识比如测试用例设计方法等价类、边界值、场景法、测试流程、缺陷生命周期再就是对搜狗产品的理解用过搜狗输入法和搜索产品能说出一些槽点和改进思路会让面试官觉得你是真对这个公司感兴趣。另外近年来“测试开发”这个概念越来越普及搜狗的测试岗也会问不少自动化测试和工具开发的问题。多了解一些主流的测试框架比如 pytest 或 JUnit了解接口测试、UI 自动化的基本思路都能在面试中拿到额外印象分。这部分的投入不会白费因为即使面不上搜狗去其他任何公司做测试岗也都会遇到类似的问题。这套题我整理完最大的感受是搜狗在2019年出的这些编程题放到今天依然不过时。它不追求偏题怪题而是把数组、字符串、双指针、边界条件这些最基础的工程能力掰开了揉碎了考。我后来自己帮团队出招聘笔试题也刻意遵循了这个原则基础扎实、边界清晰、思维严谨比刷过多少道难题重要得多。如果你能把上面这些题型的思路吃透把边界检查变成写代码的本能反应那不管是搜狗的秋招题还是其他公司的测试岗笔试题你都会有底气去应对。
返回列表