每年这个季节,都是技术岗求职者的硬仗期。商汤科技这种AI头部公司的校招笔试,向来是关注度最高的一档,尤其是它的第一场笔试——C/C++、算法开发、大数据、后端、运维、测试、数据挖掘七个方向共卷,题型跨度大、题量扎实、时间紧张,很多人考完都感觉“前半场在写算法,后半场在写作文”。这篇文章不聊八卦,只拆技术:把这场笔试背后的命题逻辑、每个方向的核心考点、典型题型和答题策略完整梳理一遍,让你备考时不在方向上偏舵。
如果你正在准备AI公司或者一线大厂的技术岗校招,特别是C/C++、算法、大数据、后端、数据挖掘这几个方向,这篇文章就是给你准备的实操复盘。我会按“命题逻辑—知识点拆解—真题复现—避坑清单”的顺序来拆,全程讲人话,尽量让你看完就知道怎么练、练什么、考场上怎么分配时间。
1. 这场笔试的命题逻辑:AI公司校招在找什么样的人
1.1 题型分布与时间分配的合理估算
商汤这类AI公司的校招笔试,虽然分了七个岗位方向,但试卷的结构高度统一:客观题(选择题)+编程题(在线OJ)+主观题(简答/设计题)三件套。选择题一般占30~40分,覆盖C/C++语法细节、操作系统、网络、数据结构、机器学习基础等;编程题2~4道,按难度递进;最后是1~2道方向相关的主观题,比如“数据挖掘岗让设计一个特征工程方案”“后端岗让画一个系统架构”都很常见。
笔试总时长通常是90~120分钟。说实话,120分钟的试卷,大部分人的实际体验是不够用。选择题每个人用时差异很大,基础好的15分钟能扫完,基础弱的能磨40分钟。编程题每道20~30分钟,主观题20分钟左右。我的经验是:选择题不要反复纠结,超过2分钟没思路就先标记跳过去,编程题先把最简单的暴力解写出来保底,主观题一定要写,哪怕写不全也比留白强。
1.2 七个方向背后的共同底层能力
七个岗位方向看起来是并列的,但笔试考察的底层能力高度重合:代码基本功、数据结构与算法、对计算机系统的理解、对海量数据的处理意识。说白了,不管你投的是C/C++开发还是数据挖掘,第一关都是在筛“这个人的工程师素质够不够”。
这也解释了为什么商汤笔试的编程题往往不区分岗位——数据挖掘岗的考生也要答C语言风格的题目,后端岗也要写动态规划。因为AI公司里岗位边界本来就模糊,算法工程师要会写工程代码,大数据开发要懂算法原理,后端可能是所有方向的地基。所以备考时千万别赌“我只复习本方向就够”,基本功是哪一关都绕不开的。
2. 核心技术点逐一拆解:每一科到底在考什么
2.1 C/C++:不是考语法,是考内存观
很多同学复习C语言就是背语法、背关键字,这就错了。商汤笔试里的C/C++题,表面考的是指针、数组、结构体,实际考的是“你在写代码时脑子里有没有内存模型”。比如经典的指针和引用区别、const修饰指针的两种读法、深拷贝和浅拷贝的区别,不是让你背定义,而是看你能不能讲清楚底层发生了什么。
举个例子,选择题里常出现这种:定义一个二维数组int a[3][4],问a+1和&a+1的区别。如果不理解数组名在表达式中的退化规则、不理解步长的概念,这道题就只能是蒙。而正确的思路是:a是int[4]类型的指针,a+1跳过4个int;&a是整个二维数组的指针,&a+1跳过整个数组,也就是12个int。这就是内存观。
让我直接给一个常见的考点清单:
- 指针运算与数组名退化规律(步长、类型、优先级)
- 结构体对齐与内存布局(sizeof的计算)
- 栈、堆、全局区、常量区的生命周期
- 野指针、悬空指针、内存泄漏的成因与规避
- 引用和指针的区别,以及函数传参的三种方式
- 位运算技巧(清位、置位、交换、判断奇偶)
- C++里还要懂RAII、智能指针、STL容器底层数据结构的取舍
很多同学觉得STL随便用就行,但笔试会问vector的扩容机制、map和unordered_map的底层差异。vector之所以是动态数组,本质是“三段迭代器+内存重新分配”,扩容时一般是1.5倍或2倍增长,每次扩容都要把老数据搬过去。而map是红黑树,有序查询O(logn),unordered_map是哈希表,均摊O(1)但无序。笔试里问你“频繁查询用哪个”,答案不一定是哈希表——如果需要范围查询,哈希表就废了。
注意:C++方向还有一个高频考点——虚函数和虚表。笔试考多态的实现原理时,不是让你说“父类指针指向子类对象”,而是让你说清楚虚函数表指针存在对象内存的哪个位置、虚函数表是类级别共享的、构造函数不能是虚函数的原因。
2.2 算法开发:从DP到深度学习的敲门砖
算法岗位的笔试题目,向来是“看起来熟悉,做起来陌生”。它不会直接考你“写一个快排”,而是给你一个经过包装的场景,让你把它抽象成算法问题。数据结构部分高频考链表、二叉树、堆、图;算法部分高频考动态规划、二分查找、贪心、DFS/BFS、滑动窗口、前缀和。
动态规划是重灾区。很多人DP学不好,是因为上来就背状态转移方程,而不是先搞明白“状态定义”。笔试题里最常见的DP套路有:二维网格走法数(状态是坐标,转移是左边和上边)、背包问题(状态是容量和物品)、最长递增子序列(状态是当前元素作为结尾时的最长长度)。我的建议是:每道DP题先写清楚dp[i]或dp[i][j]代表什么,再推导递推关系,最后想初始化和遍历顺序。这三步只要逻辑通顺,代码自己就出来了。
商汤毕竟是视觉AI起家,算法笔试偶尔会渗透一些图像/深度学习背景的题目,比如卷积层的感受野计算、池化层的作用、BatchNorm解决了什么问题。这类题一般不深,偏概念理解,主要是筛你有没有基本的AI常识。
另外,算法题的边界条件也特别爱考。比如“反转链表”里链表为空或只有一个节点的情况,“二分查找”里左闭右开和左闭右闭两种写法的死循环问题。边界条件写全,是代码从“能跑”到“不挂”的分水岭。
2.3 大数据:从海量数据里筛出真问题
大数据方向笔试的核心就是那句话——“单机装不下,怎么办”。所有题目归结起来就是四大类:海量数据排序/去重/查找TopK、数据倾斜与分区、流式计算与窗口、分布式一致性。懂行的同学一眼就能看出来,这考的是MapReduce思想、哈希分片、外部排序、布隆过滤器、Bitmap这些基础工具。
一道典型题:1亿个整数,找出重复次数最多的前100个。单机内存装不下1亿个整数直接排序,正常解法是“哈希分片”思路——先对每个数字取哈希,对100取模,把数据拆到100个小文件里,每个文件单独统计频率,再合并各文件的前100。这里考的核心是“分而治之”的工程思想,不是代码技巧。
还有一个高频概念是数据倾斜。笔试会让你分析“某个key的数据量特别大,导致Reduce阶段长期卡住”的原因和解决方案。常见的对策有:给倾斜key加随机前缀打散、对倾斜key单独处理、两阶段聚合(先局部聚合再全局聚合)。这几句话在面试里说出来,就是实打实的加分项。
我在笔试里最怕的不是大数据题难,而是同学直接用单机思维愣解。商汤这种AI公司的数据规模,一天可能是PB级,你要展现的是对分布式环境的理解。答题时哪怕写伪代码、写思路,也要把“分片—并行—归并”这个主旋律写清楚。
实操心得:备考大数据方向时,别只背名词,动手搭一个简易MapReduce流程跑一遍——比如用Python写个map函数和reduce函数,模拟wordcount,理解数据是怎么经过shuffle的。这个过程比刷十道题都有用。
2.4 后端开发:工程化能力见真章
后端岗的笔试除了算法题,主观题往往聚焦系统设计和工程实践。常见的就是“设计一个短链系统”“设计一个带过期时间的缓存”“如何保证接口幂等性”。这些题没有唯一答案,但考察的是你是不是真的做过后端,有没有踩过生产环境的坑。
先说短链系统这种经典设计题。答题的主线非常清晰:短链生成(哈希+冲突处理)、存储选型(KV存储)、重定向(301还是302)、并发量预估、缓存层设计。每个环节都能展开聊很多,比如短链一般6~8位字符,用Base62编码,预估每天千万级生成量时,单库单表能否扛住,要不要引入分库分表或分布式ID。
带过期时间的缓存,这个题我在笔试和面试里都见过。考察点在于:过期时间是存在缓存条目里还是单独维护?惰性删除和定期删除怎么选?缓存满了之后用LRU还是LFU淘汰?能写出一个线程安全的LRU(哈希表+双向链表),这就是一个很完整的答案。
后端方向的编程题还喜欢考网络和并发相关的问题:TCP三次握手和四次挥手为什么状态不同、进程和线程和协程的区别、select/poll/epoll的机制差异、什么是IO多路复用。这些属于“背了就有分”的题,但很多科班同学到毕业都说不清楚epoll和poll的本质区别——epoll用事件驱动回调机制,时间复杂度是O(1),poll是每次全量扫描O(n)。
注意:如果你是投后端方向,建议提前把Redis、MySQL、Kafka、Nginx这些中间件的基础原理过一遍,因为笔试经常以选择题形式出现,比如Redis的持久化机制有RDB和AOF、Kafka为啥快(顺序写+页缓存+零拷贝),这些都是后端开发的基本功。
2.5 数据挖掘:特征工程与模型评估
数据挖掘方向的笔试主观题,最喜欢考“给你一个业务场景,你怎么做数据建模”。比如“预测用户流失概率”“商品推荐排序”。这种题别直接上来写模型,要用标准的数据挖掘流程去答:数据理解→数据清洗→特征工程→模型选择→评估迭代。
特征工程是出题人最偏爱的考点,也是你展示工程经验的地方。缺失值处理要区分机制(随机缺失和非随机缺失处理策略不同),连续特征离散化用什么分箱策略(等距、等频、基于熵的分箱),类别特征用One-Hot还是Label-Encoding,要不要做特征交叉——这些都是能做文章的地方。
模型评估也是一个高频主题。你需要能说清楚准确率、精确率、召回率、F1、AUC的区别和适用场景。特别是有类别不平衡的数据集,比如正样本只占1%,此时准确率就是陷阱,模型全预测负样本也能拿到99%准确率,但毫无业务价值,这时候更该看PR曲线或AUC。这个分析思路在笔试里写出来,是很亮眼的。
另外一个常考细节是交叉验证:为什么用K折而不是直接用训练集测试集划分?因为小数据集上直接划分一次,结果方差太大,K折能更稳定地评估模型泛化能力。K值怎么选,一般选5或10,数据集小就加大K值,数据集大就减小K值。
3. 实操复现:三道典型真题的完整解法
3.1 C语言经典:写一个安全的字符串逆序函数
这题考C/C++基本功,看起来简单,写对不容易。要求:实现一个函数,输入一个字符串,原地逆序,不能用库函数strlen和strrev。
#include <stdio.h> void reverse(char *s) { if (s == NULL) return; char *left = s; char *right = s; while (*right != '\0') { right++; } right--; // right现在指向最后一个有效字符,不是\0 while (left < right) { char tmp = *left; *left = *right; *right = tmp; left++; right--; } } int main() { char s[] = "hello world"; reverse(s); printf("%s\n", s); return 0; }强调两个细节:第一,函数入口必须先判断指针为空,否则后续解引用就是未定义行为,在OJ上可能直接跑异常。第二,找字符串尾部时,先让right走到'\0'位置再回退一步,否则把'\0'交换到字符串头部,输出就变成空串了。这种代码,你本地测试时很难测出问题,因为习惯了用printf看结果,一旦把'\0'换到首位,printf什么都不输出,肉眼可见地错了,但还有一个更隐蔽的情况是数组越界或者访问了不该访问的地址,这在OJ上会报RE,排查起来很费时间。
很多科班同学笔试时会遇到这个问题:本地跑好好的,提交就RE。我遇到过最典型的就是把main里的char *s = "hello world"传给这个函数,试图修改字符串字面量,这会在运行时直接崩溃。因为字符串字面量存储在只读常量区,对其进行写操作,属于未定义行为,在Linux上一般会触发段错误,在Windows上可能表现成卡死或弹出程序崩溃窗口。正确做法是像代码里一样定义成char s[]数组,这样字符串会存到栈上,才可以修改。
3.2 海量数据题:如何找出出现频率最高的TopK
这题是商汤笔试的高频题,也是大数据岗的“必刷题”。题面一般是:一个超大文件里存了N个整数,内存只能容纳M个数,要求找出出现次数最多的K个数。这个场景就是典型的“内存装不下”问题,必须分治。
第一步,哈希分片。用hash(num) % P把大文件拆成P个小文件,P的取值要保证每个小文件能完全读入内存。比如你有1GB内存,每个小文件控制在100MB左右,P就取至少10。为什么用哈希分片而不是直接按数值大小切?因为同一个数值的所有出现必须落在同一个小文件里,否则统计的频率就碎了。
第二步,逐个小文件统计。对每个小文件用哈希表统计每个数出现的次数,维护一个大小为K的小根堆,堆顶是当前最小的频率值,只有当新的频率大于堆顶时才替换。最终每个小文件都得到一个TopK。
第三步,合并。把P个小文件的TopK汇总,再做一次排序或堆筛选,得到全局TopK。
#include <bits/stdc++.h> using namespace std; // 用最小堆求TopK,堆里保存频率最大的K个元素 void addToHeap(priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>>& minHeap, int num, int freq, int k) { if (minHeap.size() < k) { minHeap.push({freq, num}); } else if (freq > minHeap.top().first) { minHeap.pop(); minHeap.push({freq, num}); } }这一步就完了?还差最重要的一句话。无论代码还是伪代码,你得把“为什么用哈希分片而不是直接排序”“为什么用小根堆而不是大根堆”写明白。小根堆大小固定为K,堆顶永远是当前TopK里的最小值,新元素只要比它大就替换,这样堆里始终维护的是当前见过最大的K个,复杂度O(nlogK),要比全排序的O(nlogn)快很多。这反映的是工程思维,不是纯算法思维——知道K很小,所以用堆是划算的。
3.3 系统设计题:设计一个支持过期时间的键值缓存
后端和运维方向的主观题,这题我强烈建议大家提前准备。因为无论投哪家公司,缓存设计都是高频考点。答题的框架要清晰:API设计、存储结构、过期策略、并发控制。
API设计很简单:put(key, value, ttl) 和 get(key)。get过期返回空。存储结构用哈希表存key到缓存的映射,缓存项里包含value和expireTime(过期时间戳)。这里关键点来了——判断过期,是用“懒删除”还是“定期删除”?
Redis的答案可以作为范本:惰性删除+定期删除结合。惰性删除是get的时候检查expireTime,过期了就删掉并返回空;定期删除是每隔一段时间随机抽查一批带过期时间的key,过期就删。为什么要这样结合?纯惰性删除会导致大量过期key一直占内存直到被访问,内存可能被“垃圾”占据;纯定期删除又需要额外线程,而且如果key特别多,全量扫描太耗时。两者结合,既保证了过期key能及时回收,又避免了频繁全量扫描。
代码层面,还要考虑线程安全。最朴素的做法是给整个缓存加一把大锁,写起来简单但并发性能差。进阶一点用读写锁:读多写少场景下,读锁可以并发,写锁互斥。再进阶就是分段锁,把哈希表分成多个桶,每个桶一把锁,减少锁竞争。
public class CacheItem { String value; long expireTime; // 绝对的过期时间戳,单位ms public CacheItem(String value, long ttl) { this.value = value; this.expireTime = System.currentTimeMillis() + ttl; } public boolean isExpired() { return System.currentTimeMillis() > expireTime; } }特别提醒:存过期时间时,一定要存“绝对过期时间戳”而不是“相对存活时长”。因为在并发场景下,如果存相对时长,取的时候还要重新计算——谁减谁、延时多少,很容易因为代码写错导致误删或活数据提前过期。存绝对时间戳,每次判断就是一个简单的比较,语义清晰。这种细节也是笔试阅卷时能拉开差距的地方。
4. 笔试常见问题与避坑清单
4.1 编码习惯与编译运行陷阱
先列一个我当面试官、也当考生时都踩过的错题集,都是真实高频的失分点:
- 缺头文件:用了malloc不包含stdlib.h、用了bool不包含stdbool.h,本地可能因为编译器宽松过,提交环境严格就直接CE(编译错误)。
- 数组越界:很多题数据范围n<=100000,你开了int a[100000],访问a[100000]就已经越界了。这种错误本地不报错,但在Linux系统上可能恰好踩到未映射的页导致RE。
- 多组输入输出:做题之前先看题目的输入描述。是单组还是多组测试,多组测试时循环条件怎么写。很多人栽在这里不是因为不会做,而是因为输出格式不符被判定WA。
- 死循环与超时:写了while(1)忘了break、递归没有终止条件,OJ上直接TLE。写完代码先扫一遍循环出口是否存在。
- 大数问题:int溢出,比如两个1e9相加就溢出了,要用long long。有些题明知道会溢出,还是有人用int交上去,白白丢分。
注意:在线笔试时,代码跑不跑得通是一回事,代码写不写得“干净”是另一回事。有些系统有代码查重和风格审阅,就算没查重,面试官后续也会看到你的笔试代码,写一手规范的缩进和命名,至少不会在后面的面试轮次里成为扣分项。建议平时就养成好习惯——变量名用完整英文单词,不要用a、b、c、tmp一路走到底。
怎么训练?刷题时多做“多语言+多环境”测试。我备考时,每道题都在本地用gcc编译一次(加上-Wall -Wextra警告选项),再提交到OJ上看结果。这样能把大部分编译警告和运行时崩溃提前暴露出来。很多同学直到笔试前都没在Linux环境下编译过C/C++代码,上了考场才发现连头文件大小写错了都能CE,特别亏。
4.2 答题策略:先保底、再优化、最后补设计
笔试考场上最大的敌人是“完美主义”。尤其是编程题,总想着一次写出最优解,结果前两题耗时太久,后面的题只能空着,更亏。我的稳定策略是分三轮走:
第一轮:用5~10分钟把所有题目全部看一遍,判断题目的难度和熟悉度,在草稿纸上标出“优先做”和“最后做”的顺序。编程题先做思路最清晰的那道,哪怕用暴力解法先过一遍样例,先把分数拿到手里,这道题至少不是零分。
第二轮:把暴力解法优化到正解。比如第一轮用两层循环暴力求TopK,第二轮改成小根堆O(nlogK)。要记住一个原则——OJ判题看的是最终的运行结果和运行时间,不是看你第一版的代码,所以你可以先在编辑器里把暴力解写出来理清逻辑,再在这个基础上改。
第三轮:回到主观题。主观题通常是系统设计或场景分析,评分标准里“思路完整”的权重远高于“方案完美”。哪怕你写不出全文,也要把自己的思考链路写出来:先用什么,遇到瓶颈怎么办,再加什么。阅卷人最怕看到空白,最不怕看到有逻辑的草稿。
时间分配上,我个人的经验值供参考:30%时间给选择题、50%时间给编程题、20%时间给主观题。如果你的岗位方向是C/C++或算法,把编程题的权重再调高一些;如果是运维/测试方向,主观题的权重可以适当往上加,因为这两个方向更关注你在遇到问题时的排查思路。
4.3 从笔试到面试:拿什么状态去衔接
笔试结束不代表备考结束。真正拉开差距的,是笔试后到面试前这段时间的复盘。我见过太多人笔试完了就扔了,结果面试被问“你笔试最后一题怎么设计的”时支支吾吾,直接给人留下“不是自己写的”的负面印象。
考完的当天或者第二天,做三件事:第一,把自己笔试时卡住的题重新做一遍,搞清楚思路;第二,把主观题答案重新组织一遍,写成一份正式的文档,因为面试官很可能就拿着你的答卷追问;第三,做一套模拟面试问答,把每道题的“为什么”准备好。比如你写了“用哈希分片解决海量数据”,就得能解释为什么用哈希而不是范围分片——哈希能让相同key的数据稳定落在同一个桶,而范围分片在数据分布偏斜时个别桶会特别大,失去分片的意义。
另外一个容易被忽略的环节是介绍项目经历。智能硬件公司或AI公司的面试官,尤其看重候选人有没有比较完整的项目经验。技术栈是不是最前沿不重要,重要的是你是不是真的搞懂了自己做的系统的每个环节。如果你在简历上写了“负责XX系统的后端开发”,最起码要把以下问题准备一遍:系统的QPS是多少?遇到的最大挑战是什么?有没有出现过线上事故?怎么处理的?被追问到细节时,一定不要用“这个我不太清楚”来搪塞——回答不清楚的地方比诚实的小失误更减分。
5. 最后再分享一点实践经验
写到这里,我特别想说一句:商汤这场笔试,它的题目本身并不是为了刁难你,而是在快速筛选“扎实”的人。做对几道题不代表你一定会被录用,但如果你在C/C++上指针都不熟、在大数据上只知道Hadoop三个字母、在后端上说不清缓存和数据库的一致性,那确实说明还没准备好。
我自己的备考经验里,最有用的“捷径”是——把每道错题都变成一张知识卡片。比如这道题考了“为什么map底层是红黑树”,我就把红黑树的特点、和AVL树的区别、为什么STL选它不选AVL,全写到卡片上。到笔试前,我的知识卡片可能不到100张,但每张都覆盖一个足够深的考点。面试时被问到相关话题,脑子里的知识结构是树形的,不是零散的碎片,追问到哪一层都能接得住。
还有一点是工程习惯要从平时做起。考试的时候你是没有调试器的,所有逻辑得靠脑子推演。平时写代码就用“先想边界、再写主体、最后补注释”的套路,到考场上才能在不调试的情况下一次把代码写到七七八八。很多同学平时依赖IDE自动补全和单步调试,笔试一关调试器就慌了,这就是平时训练方式出了问题。
如果你正在准备AI公司或大厂的校招,不管你投的是C/C++、算法开发、大数据、后端还是数据挖掘方向,把基本功打扎实永远是性价比最高的策略。笔试只是第一道门,真正让你走得更远的是对技术的理解深度,以及遇到问题时的工程化解决思路。希望这篇复盘能帮你少走几步弯路,考场上多稳几分。