公司动态

爱奇艺C++校招笔试复盘:字符串、链表与底层基础全解析

📅 2026/8/31 18:28:02
爱奇艺C++校招笔试复盘:字符串、链表与底层基础全解析
爱奇艺2018秋季校招的C工程师笔试我印象里第三场是十月中旬的那批。当时在线笔试系统用的还是牛客网那套一共三道编程题加一堆选择填空时间卡得比较紧。现在回头看那场笔试题的风格很有代表性不堆偏题怪题但非常看重基本功的扎实程度尤其是对C内存模型和STL底层行为的理解。我身边几个一起参加的朋友有人刷了大量LeetCode却在选择题上翻车也有人编程题AC了但基础题答得稀烂最后没进面试。这篇文章我想还原一下第三场笔试的整体情况把考点、思路和我在实战中踩过的坑都整理出来给正在准备C/C方向校招的同学做个参考。1. 整体考情复盘与命题思路拆解1.1 第三场笔试试卷结构与时间分配爱奇艺的校招笔试和绝大多数互联网公司一样用的是在线OJ平台第三场C工程师的试卷结构大致如下单选多选混合的基础知识题差不多20道占总分四成左右覆盖C语法、数据结构、操作系统、网络基础剩下的全是编程题通常是三道难度梯度很明显第一道是字符串处理或者简单模拟第二道是排序或链表操作第三道则是状态搜索或复杂DP。总分100分编程题权重很大基本是能不能进面试的分水岭。当时我拿到试卷后的第一反应是时间不够用因为选择题里藏了不少需要仔细推敲的C细节题。比如有一道考的是vector扩容时迭代器失效的问题有一道考的是虚函数表的内存布局还有一道考的是std::string的COWCopy-on-Write在不同编译器下的表现差异。这些题如果是靠记忆去猜很容易翻车。我的策略是先把选择题快速过一遍拿不准的先标记然后直接去做编程题因为编程题一题的分值顶得上五六道选择题性价比更高。编程题全部AC之后再回头啃那些选择题。1.2 校招笔试命题的几个潜规则经历过几场校招笔试后我发现爱奇艺这类公司出题有几个固定套路。第一非常喜欢考看起来简单但边界条件极多的题比如字符串逆序这类题看起来谁都写得出来但真正能一次通过的并不多因为空格、标点、多字节字符这些边界情况会被拿来测试。第二STL容器相关的问题从来都是重点因为实际工程里C工程师天天和容器打交道如果一个候选人连map和unordered_map的区别都说不清楚面试官会怀疑你能不能胜任业务开发。第三操作系统和网络相关的题一般不会出得太深但一定会出比如进程和线程的区别、TCP三次握手的状态变化、堆和栈的区别这些是计算机基础的核心属于必考范畴。另外一个容易被忽略的点是笔试时间通常是和同期其他公司冲突的。我记得爱奇艺第三场那天网易游戏和美团点评的笔试也在同一时间段。所以决定参加哪一场之前一定先确认好时间。我当时的选择是优先爱奇艺因为内容偏底层和我平时准备的方向更契合事实证明这个选择是对的。2. 编程题核心实战从思路到AC代码2.1 字符串逆序输出最简单的题也最考验细节第三场第一道编程题是字符串逆序输出题目描述大概是这样输入一行字符串将字符串逆序输出要求空间复杂度为O(1)。这道题看着简单实际上一半的人会挂在边界条件上。标准做法是双指针从两端交换字符#include iostream #include string #include algorithm int main() { std::string s; std::getline(std::cin, s); // 注意用getline而不是cin因为字符串里可能有空格 int left 0, right (int)s.size() - 1; while (left right) { std::swap(s[left], s[right]); left; right--; } std::cout s std::endl; return 0; }这里有一个关键的输入坑题目说一行字符串但没明确说包不包含空格。如果你用std::cin s读入遇到空格就断了导致hello world只会逆序输出world直接Wrong Answer。正确做法是用std::getline整行读入。我当时第一版代码就栽在这个地方好在提交前测了一下带空格的用例才改过来。另外如果题目要求原地逆序且不允许用额外数组那双指针从头尾向中间逼近是最合适的时间复杂度O(n)空间复杂度O(1)。这道题还有一个容易忽略的细节是字符类型。题目只说了字符串没说一定是ASCII字符。如果输入包含中文等多字节字符直接用std::swap去交换char就会把UTF-8编码的多个字节拆散导致乱码。第三场没有考到这个深度但面试环节可能会追问提前想清楚更好。2.2 冒泡排序的优化从手写排序到理解排序本质第二道编程题我记得和冒泡排序有关。原题是给一个数组要求用冒泡排序将其升序排列并输出每一轮排序后的结果。这题一看就是考基本功但要求输出每轮结果这一点非常关键。如果你在循环里漏了某一轮或者边界范围算错了输出的中间过程对不上照样不得分。我当时写的版本#include iostream #include vector void bubbleSort(std::vectorint arr) { int n (int)arr.size(); for (int i 0; i n - 1; i) { bool swapped false; for (int j 0; j n - 1 - i; j) { if (arr[j] arr[j 1]) { std::swap(arr[j], arr[j 1]); swapped true; } } // 输出本轮结果 for (int k 0; k n; k) { if (k) std::cout ; std::cout arr[k]; } std::cout std::endl; if (!swapped) break; // 提前终止 } }冒泡排序本身没什么难度但这道题考了一个容易被忽视的点当数组已经有序时冒泡排序应该提前终止。加了一个swapped标记之后时间复杂度最好就是O(n)而不是O(n^2)。在校招笔试里时间复杂度的分析会体现在后面的大题或者面试追问里。比如你写了冒泡排序面试官可能会问它的最好情况时间复杂度是多少怎么优化如果你当时答不出swapped标记这个优化印象分会打折扣。另外一个细节是输出格式。题目要求输出的数字之间用空格分隔末尾换行。这看起来是小事但真的有人因为多输出了一个空格而Wrong Answer。在线OJ对格式非常敏感末尾空格、空行都算错误写代码的时候就要刻意避免。2.3 链表反转这道题我推荐用三指针迭代法第三场最后一道编程题是链表反转。题目给一个单向链表要求返回反转后的头节点。这道题属于链表操作里最经典的题型和字符串逆序并列是各大厂校招笔试的常客。难点不在于实现本身而在于边界情况的处理空链表、只有一个节点的链表、多个节点的链表三种情况都要能正确返回。我推荐用迭代三指针法因为空间复杂度是O(1)而且不容易弄混指针指向。核心思路是用prev记录上一个节点curr记录当前节点next提前保存下一个节点防止链断掉。struct ListNode { int val; ListNode* next; ListNode(int x) : val(x), next(nullptr) {} }; ListNode* reverseList(ListNode* head) { ListNode* prev nullptr; ListNode* curr head; while (curr ! nullptr) { ListNode* next curr-next; curr-next prev; prev curr; curr next; } return prev; }这道题在笔试里占的分值很高而且还会考察你是否能识别出边界条件。空链表直接返回nullptr单节点链表实际上循环一次就直接返回了原节点。整个算法的复杂度是O(n)时间、O(1)空间。面试时经常会有追问用递归怎么写如果链表中存在环还能反转吗递归版本代码量少但空间复杂度是O(n)在深度较大的链表上可能导致栈溢出。是否存在环这个问题一般先判断环再决定是否反转实际工程中一定要考虑。3. C语言基础考点盘点选择题中的陷阱3.1 指针与内存管理C/C工程师必须越过的那道坎爱奇艺第三场笔试的选择题里指针和动态内存管理的占比特别高。我印象最深的一道题是定义一个二维数组int a[3][4]问a1、a1、*(a1)、*(a1)1分别代表什么。这道题看似简单实际上把数组指针、指针数组、数组名退化这些概念全部考了一遍。正确答案是a1指向第二行的首地址即a[1]的地址a1指向整个数组之后的位置移动了3*4个int的大小*(a1)等价于a[1]是一个一维数组名退化为指向其首元素的指针*(a1)1指向a[1][1]。这些都是《C和指针》这本书里的经典内容没深入理解数组和指针关系的同学很容易在这里翻车。内存管理另一个必考点是new/delete与malloc/free的区别以及内存泄漏的产生场景。笔试喜欢给一段代码里面new了数组但用delete释放问会发生什么。正确答案是这是未定义行为可能导致程序崩溃或内存泄漏。正确写法是new[]配delete[]new配deletemalloc配free。我在第三场碰到的版本是用malloc分配内存后用delete释放这同样是未定义行为因为delete会调用析构函数而free不会两个释放函数的管理机制也不同。关于内存管理我想多说一句笔试很多时候不只是考你会不会写代码还考你有没有工程习惯。比如new之后判断返回指针是否为空在现在的C里其实不太必要因为new失败会抛出std::bad_alloc异常但在嵌入式环境里异常机制可能被禁用这就要靠返回值判断了。我在笔试里遇到的一道题就是关于这个问的是在没有异常机制的环境里如何安全地分配内存。3.2 while与do-while的区别选择题必背但容易混淆热词里有一个c语言while和do-while区别这确实是笔试选择题的常客。核心区别一句话就能说清while先判断条件后执行循环体条件为假时循环体一次都不执行do-while先执行一次循环体再判断条件至少执行一次。但笔试不会只问定义它会给代码片段让你判断输出。比如int i 0; while (i 0) { i; } printf(%d\n, i); // 输出0 int j 0; do { j; } while (j 0); printf(%d\n, j); // 输出1这里要特别注意do-while后面的分号不能漏漏了编译直接报错。实际工程里do-while还有一个非常经典的用法——宏定义多语句安全包裹。比如#define DO_SOMETHING(a, b) do { \ int tmp (a); \ (a) (b); \ (b) tmp; \ } while(0)这个宏加上do-while(0)之后无论外层怎么套if都能安全展开而不会因为分号匹配问题编译报错或者改变逻辑。这个点在笔试里可能会以宏定义相关的形式出现面试中也常被问到。3.3 字符串函数的隐秘边界strcpy、strcat、memcpy的异同C语言字符串处理函数是选择题的常客热词里也出现了c语言字符串函数、字符串逆序c语言pta这些搜索词。第三场笔试中有一道题是问strcpy、strncpy、memcpy三者有什么区别。这题看似简单但能完全答对的人不多。strcpy按\0判断拷贝结束不拷贝\0之后的任何内容也不指定拷贝长度存在缓冲区溢出风险。strncpy多了一个长度参数但它并不会在源字符串长度不足时自动补\0而且如果拷贝长度超过源字符串长度它会用\0填充剩余空间这个行为很多初学者不知道。memcpy按字节数拷贝不关心字符串语义适合拷贝任意内存块但要求dest和src不能重叠重叠时应该用memmove。笔试里还喜欢考strlen和sizeof的区别。strlen是运行时计算字符串长度不包含结尾的\0sizeof是编译期计算对数组返回整个数组占用的字节数对指针返回指针本身的大小64位系统下为8。所以对char buf[] hello执行sizeof(buf)返回6执行strlen(buf)返回5。这个考点在选择题里出现频率非常高几乎每场笔试都有。4. 机试实战障碍开发环境与调试经验复盘4.1 在线笔试环境下的编译器和代码规范问题爱奇艺第三场用的在线编程系统编译器版本是GCC 7.3默认使用C14标准。这个信息很关键因为如果你用了C17才支持的语法特性比如std::optional、结构化绑定、if constexpr编译就会直接报错。我当时在写第二题的时候顺手写了一个auto [a, b] std::make_pair(1, 2)编译直接挂了愣了一下才想起来这是C17的语法赶紧改成传统写法。还有一个细节是scanf_s和scanf的区别。在线OJ系统大多跑在Linux上使用的是glibc只支持标准C的scanf不支持Visual Studio的scanf_s。但许多同学平时在Windows上用VS写代码习惯了一上来就写scanf_s在OJ上直接编译失败。我的建议是校招复习阶段就全部用标准C/C写不要依赖Windows特定的函数或头文件。另外头文件包含顺序和命名空间使用也要注意。虽然这些不影响编译通过但有些OJ平台会使用-Wall -Werror参数把警告当作错误处理比如未使用的变量声明就直接编译失败。我的习惯是写代码时顺手把不用的变量删掉不放过任何一个警告。4.2 本地开发环境配置VSCode跑通C比你想的更省心热词里有好几个是关于VSCode配置C/C环境的这说明很多同学对本地开发环境搭建不太熟悉。尤其是校招季大家都在宿舍或者图书馆用笔记本刷题如果不能快速搭好本地环境刷题效率会大打折扣。我个人的主力编辑器就是VSCode配置C/C环境其实很简单装两个扩展就行C/C微软官方出的那个和Code Runner。前者提供语法高亮、代码补全和调试功能后者可以一键编译运行单个文件。编译这块我推荐直接用g命令而不是依赖IDE的构建系统。写一个小脚本或者直接用终端跑g -stdc14 -Wall -O2 main.cpp -o main ./main参数解释一下-stdc14指定C标准-Wall开启所有常见警告-O2开启优化。笔试机试一般不会开-O2但平时刷题开着有助于暴露潜在问题。我经常用-Wall来抓那些被忽略的类型转换和未初始化变量问题这些在笔试的隐藏测试用例里很可能就是扣分点。VSCode里调试配置也不复杂按F5先选C (GDB/LLDB)系统会自动生成launch.json和tasks.json把program改成你的可执行文件路径就行。调试时设置断点、查看变量值在线下复现问题的时候非常有用。笔试现场虽然不能用这些调试工具但平时刷题用熟了之后代码的容错性会明显提升。4.3 从C盘爆红到开发环境清理一次机房电脑的求生实录热词里有一堆c盘满了怎么清理、c盘爆红了可以删除哪些文件、磨针c盘清理之类的搜索词。很多人觉得这跟校招笔试没什么关系但正是在第三场笔试的准备阶段我借了一台机房电脑来模拟在线笔试环境结果C盘直接爆红了系统都提示磁盘空间不足无法创建新文件。当时的应对方案我记了下来因为这些都是开发过程中非常实用的经验。机房电脑经常中招的点是用户目录下的AppData\Local\Temp缓存文件以及C:\Users\Administrator\AppData\Local下各种软件的缓存文件。我当时的处理思路是先删除Temp目录下能删的文件删除被占用文件时注意提示然后是各种软件在AppData里生成的缓存最后清理系统休眠文件hiberfil.sys。这里要注意直接删C:\Windows下的系统文件是危险操作千万不要碰。如果你遇到的是C盘空间不足导致编译失败更稳妥的做法是把VSCode的扩展和编译缓存都挪到其他盘或者用DiskGenius从其他盘扩容。这些操作虽然看起来和笔试无关但真遇到设备不给力的时候能帮你节省宝贵的调试时间。我当时清理完之后本来已经跑不动的npm install和编译任务都能正常完成了。笔试现场最怕的就是环境问题提前把系统盘空间清理好等于给自己少埋了一个雷。5. 经典C笔试题型拓展与备战复盘5.1 文件读写操作笔试和面试都容易忽略的工程基础热词里出现了c语言文件读写操作代码这其实也是校招笔试的重要考点。爱奇艺第三场虽然没直接考文件读写的大题但选择题里有一道是和文件操作相关的C代码题问的是读取文件时如何判断文件是否成功打开。C语言风格的文件操作核心是FILE*指针加fopen/fclose。正确写法是FILE* fp fopen(data.txt, r); if (fp NULL) { // 处理打开失败的情况 perror(fopen); return -1; } char buffer[256]; while (fgets(buffer, sizeof(buffer), fp) ! NULL) { // 按行处理 } fclose(fp);C风格则是用ifstream/ofstream#include fstream #include iostream #include string int main() { std::ifstream in(data.txt); if (!in.is_open()) { std::cerr Failed to open file std::endl; return 1; } std::string line; while (std::getline(in, line)) { // 按行处理 } return 0; }文件操作的坑在于缓冲区和资源释放。fscanf读字符串时忘记指定最大读入长度会造成缓冲区溢出fclose忘记调用系统会在程序结束时回收但如果你在一个长时间运行的服务里反复打开文件不关闭文件描述符会耗尽导致后续打开失败。这些都是工程上非常经典的问题笔试虽然只考一两个代码片段但背后考察的是工程安全意识。5.2 Dijkstra算法的笔试姿势不要只背模板热词里有迪杰斯特拉c这个也是校招笔试的高频考点。爱奇艺第三场第三道编程题其实就和图的最短路径沾边。这里我要分享一下亲身体会大部分同学都能背出经典Dijkstra算法的模板但笔试题目里大多数时候不会直接给你一个标准最短路让你套而是把它包装成一个业务问题比如从起点到终点的最小花费服务器之间的最小延迟等等。识别出这是最短路问题才是解题的关键。经典Dijkstra实现优先队列优化大概是这样的#include vector #include queue #include limits using namespace std; void dijkstra(int start, const vectorvectorpairint, int graph, vectorint dist) { int n (int)graph.size(); dist.assign(n, numeric_limitsint::max()); dist[start] 0; priority_queuepairint, int, vectorpairint, int, greaterpairint, int pq; pq.push({0, start}); while (!pq.empty()) { auto [d, u] pq.top(); pq.pop(); if (d dist[u]) continue; for (auto [v, w] : graph[u]) { if (dist[u] w dist[v]) { dist[v] dist[u] w; pq.push({dist[v], v}); } } } }注意里面有一个if (d dist[u]) continue;的剪枝操作。如果不加这个判断优先队列里可能积累大量过期的冗余节点导致复杂度爆炸。这个细节也是面试官很喜欢追问的为什么Dijkstra算法需要贪心选择当前距离最小的节点如果图里有负权边会怎样如果用的是邻接矩阵而不是邻接表会怎样这些追问的来源都是笔试里那道基础题。5.3 从笔试到面试那些额外的能力其实更重要爱奇艺第三场笔试虽然只是一场考试但它的分数构成和面试评价体系是高度一致的。你会发现编程题AC得漂亮的人往往在选择题部分也不会太差因为两者考察的是同一种能力对C语言和数据结构有没有深入的理解能不能在短时间内写出没有明显bug的代码。我个人的建议是算法刷题可以继续但别盲目。前40道题用分类刷法搞清楚每一类题的通用解法比如链表类题基本都是双指针或者哨兵节点树上题基本都是递归或者栈模拟区间类题基本就是排序加遍历图的最短路类题基本都是Dijkstra。40道之后开始刷套题模拟真实笔试场景限定时间做完整套试卷不是为了做对而是训练自己在时间压力下快速审题、快速定位考点、快速写出第一版可运行的代码。说实话现在回头看刷200道题的意义远不如认真复盘20道题来得大。另外我强烈建议在正式参加笔试之前去心仪公司的笔试系统里做一两套模拟题看看环境。很多平台在线编译器的错误提示和本地编译器不一样比如输入输出格式上多了一个\r都可能导致错误。这件事提前做一次到了真实笔试的时候心态会稳很多。6. 写在最后一次笔试给我的三个收获我后来顺利拿到了爱奇艺的面试邀请虽然最终因为个人原因没有接offer但第三场笔试的备考过程对我的C功底提升确实是实打实的。我觉得这次经历最大的收获有三个第一编程题没有捷径只有亲手写过一遍把边界条件都考虑进去才算真正掌握一道题第二C基础概念必须体系化地去理解靠死记硬背应付不了那些看起来简单但细想全是坑的选择题第三环境准备和基本功同样重要无论是本地开发环境还是C盘空间这种看似不相干的小事都有可能在关键时刻成为你翻车或者不翻车的分野。如果你也在准备类似公司的校招笔试我的经验是别急着刷难题偏题先把基础概念吃透。字符串、内存、指针、排序、链表、最短路这些核心考点全部掌握笔试的胜算就已经非常大了。剩下的细节交给时间和题量去磨。祝顺利。