ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

爱奇艺C方向校招笔试题解析:从指针内存到算法备考全攻略

爱奇艺C方向校招笔试题解析:从指针内存到算法备考全攻略 爱奇艺2020校招C方向笔试题第一场这个标题在牛客网、应届生求职论坛上一挂出来当年不知道让多少投递客户端、服务端、音视频方向的同学心头一紧。我连续几年帮学弟学妹做校招模拟和简历辅导发现大家对这类大厂C/C笔试题的认知普遍存在偏差要么觉得“只要刷LeetCode就够了”要么以为“考的都是C语言语法细节”结果一上考场就被选择题里的内存题、编程题里的输入输出格式搞懵。这篇文章我不会直接给你贴一份网传的题目列表——那份东西网上到处都有但只记题号不弄懂背后逻辑换一套题照样不会。我想从“出题人视角”出发把爱奇艺这类视频互联网公司C方向笔试第一场的题型结构、考察点、易错处和备考路线彻底拆开让你看完之后能自己判断考点、自己推导解法。这场笔试整体上分两部分一部分是计算机基础选择题涵盖C/C语言、数据结构、操作系统、网络、数据库另一部分是两道左右的手写编程题一般围绕字符串、链表、二叉树、排序、动态规划这些经典题型展开。和纯算法岗不同C方向笔试题更看重语言底层功底和内存管理能力毕竟爱奇艺的播放器、CDN调度、服务端中间件都有大量C/C代码线上问题追查到最后往往就是指针越界和内存泄漏。这篇文章就按我自己拆题的逻辑来写每一章都会讲清楚考点背后的“为什么”也会给出可以直接上手的代码模板和避坑要点。1. 先拆出题逻辑为什么C方向笔试考这些1.1 题型构成与时间分配的真相爱奇艺2020校招C方向笔试题第一场采取的是牛客网在线笔试形式总时长一般是90到120分钟。题型大致可以分成三类不定项选择题、C/C语言改错或输出题、手写编程题。选择题覆盖范围比很多人想象中广。除了C语言本身的语法和指针题还会有数据结构栈、队列、二叉树遍历、哈希冲突处理、操作系统进程线程、死锁、虚拟内存、计算机网络TCP/UDP、HTTP状态码以及少量数据库索引相关的题目。很多人备考时只刷编程题忽略了这部分实际上选择题在整张卷子里的占比通常能达到40%到50%是决定能不能进面试的关键盘。编程题一般出两道偶尔三道。第一道往往是字符串或数组处理难度偏低考察的是基本编码能力比如字符串逆序、回文判断、字符统计、去重排序第二道会上升到链表、二叉树或者动态规划像链表反转、合并有序链表、二叉树层序遍历、最长公共子序列都是高频题。少数时候会考到图论比如单源最短路径这也是爱奇艺这类做内容分发和网络调度的公司比较偏好的方向。我把这类笔试的时间分配建议给你选择题30到40分钟内必须做完拿不准的先标记跳过不要在一道题上耗太久编程题每道留25到30分钟先写暴力解拿部分分再考虑优化。在线判题系统通常按测试用例给分即使AC不了全部用例过了50%也比交白卷强太多。1.2 出题人真正想筛选的能力很多同学以为笔试就是考“会不会做题”这个理解太浅了。大厂校招笔试的本质是“低成本初筛”。面试官一天要看几百份简历不可能每份都认真面笔试是第一道过滤网它的目的是在最短时间内筛出三类人编程基本功扎实的、算法思维成体系的、工程意识靠前的。C方向尤其强调第一类“基本功”。同样是反转字符串会写for循环的人不少但能在O(1)额外空间内用双指针完成同时在边界条件空串、单字符、含空格上不翻车的人才算真正掌握。这个能力不是刷几道题能练出来的需要你对指针、数组、字符串在内存中的表示有本能级的敏感。工程意识体现在哪里体现在变量命名、代码结构、边界处理、错误返回值上。在线笔试确实只看运行结果但如果你写的代码连自己都看不下去那大概率边界条件也会漏。面试官在后台是可以看到你每次提交记录的——谁一次通过谁反复调试报错这本身就是个隐性评分维度。还有一点容易忽略爱奇艺这样的视频平台C/C岗位实际上分布在多个业务线有做客户端播放器的、有做服务端API的、有做音视频编解码的、还有做推荐系统引擎的。所以笔试题目偶尔会带一点点音视频或网络相关的背景比如让你处理媒体流数据块的合并、实现一个带超时控制的网络连接池。遇到这种题别慌核心算法还是那几板斧只是场景换了个壳。2. 字符串、指针与内存C方向“基础分”必须全拿2.1 字符串类题目的高频考法与代码模板字符串处理是C方向笔试题的第一道编程题最爱出的类型也是选择题的重灾区。因为C语言中字符串就是char数组它天然牵扯到指针运算、内存布局、结尾标志‘\0’等一系列问题一道题能同时考察多个知识点性价比极高。字符串逆序是当之无愧的Top 1高频题。我见过不少同学用最直观的方式申请一个新数组从尾部往前拷。这当然对吧但如果你在C语言笔试题里这么写至少暴露了两个问题第一额外空间复杂度O(n)不优雅第二没有考虑原地操作的能力。标准做法是双指针从两头往中间交换void reverseString(char* s, int len) { if (s NULL || len 1) return; int left 0, right len - 1; while (left right) { char tmp s[left]; s[left] s[right]; s[right] tmp; left; right--; } }这里有个关键细节传入的len是字符串有效长度通常由strlen函数得到。如果你用sizeof(s)那就大错特错了——在函数参数里数组名退化为指针sizeof得到的是指针大小8字节或4字节不是数组长度。这个知识点几乎每年都会出现在选择题里。再看字符串逆序的变种题比如“按单词逆序单词内部顺序不变”。例如输入“I love coding”输出“coding love I”。这题的经典解法是两轮反转先整体反转整个字符串变成“gnidoc evol I”再对每个单词单独反转回来。这里又涉及怎么切分单词边界要注意空格可能是多个连续空格有的题目会额外规定去掉首尾空格。这种变体题考察的就不再是单纯的语法而是“拆分问题”的能力。另外一类常见题是字符统计。给定一个字符串找出出现次数最多的字符或判断两个字符串是否是异位词。字符统计的通用模板是用一个int数组当哈希表因为ASCII字符就128个扩展ASCII 256个int count[128] {0}; for (int i 0; s[i] ! \0; i) { count[(unsigned char)s[i]]; }注意这里我用了(unsigned char)强转。为什么如果char在有符号平台上恰好是负数用它做数组下标会越界访问这是UB未定义行为。这类细节考官不会明说但你写对了就说明真正懂。再补充一个容易被忽视的点牛客网或赛码网这类在线笔试系统输入输出格式和LeetCode完全不一样LeetCode是给你函数签名填空而校招笔试往往是让你写完整程序自己处理标准输入输出。字符串题最常见的是用scanf(%s, str)读入但它遇到空格就停了如果你要读一行含空格的字符串需要用fgets(buf, sizeof(buf), stdin)然后手动把结尾的换行符去掉。这个细节每年都有大量人踩坑代码逻辑全对就是死在输入上。2.2 指针和内存管理的致命陷阱C方向笔试题的选择题部分指针和内存是必考且占比最高的部分。爱奇艺这类公司实际开发中经常要处理音视频帧缓冲、流媒体数据块这些全是裸指针操作所以考指针合情合理。先出一道最经典的送命题char *p hello; p[0] H;能不能通过编译能通过编译但运行时会崩溃或产生未定义行为。因为字符串字面量存储在只读区.rodata段试图修改它属于UB。正确的定义是char p[] hello;这样字符串会存储到栈上可以修改。这个区别我在面试模拟时几乎每次都会问能一次答对的人不到三成。再考一个int *p (int*)malloc(sizeof(int) * 10);然后free(p);再p[0] 1;会怎样这就是经典的悬空指针问题free之后p并没有被置空它仍然指向一块已释放的内存访问它是UB。正确的做法是free之后立即将p置为NULL。有的选择题会在这个基础上再挖坑比如问free(NULL)是否安全——答案是安全的标准库明确允许free(NULL)不做任何事。内存泄漏也是高频考点。比如char* func() { char* p (char*)malloc(100); strcpy(p, hello); return p; } int main() { char* q func(); // 忘记free(q) return 0; }严格说这题如果只看程序运行结果确实没问题但它是个内存泄漏示范。选择题通常会问“这段代码有什么问题”选项里会混入“返回局部变量地址”“数组越界”“内存泄漏”等。有同学看到func返回了一个指针就认为“返回局部变量地址”有误其实返回的那个局部变量是p本身p指向的堆内存是有效的真正的问题是在main里没free。如果函数改成char p[100]; strcpy(p, hello); return p;那才是返回局部数组地址的错误。C语言选择题里还有一类必考组合strlen和sizeof的对比、结构体对齐、const和指针的组合。以结构体对齐为例struct A { char c; int i; char d; }; printf(%zu\n, sizeof(struct A)); // 在32位/64位平台通常是12而不是6因为编译器会在char后面填充3个字节对齐到4字节边界。做题技巧是记住结构体大小必须是最大成员对齐数的整数倍且每个成员起始偏移量必须是自身大小的整数倍。有些公司笔试会告诉你默认对齐规则一般是4字节或8字节如果不告诉默认按最大成员对齐计算。提示这类基础题没有捷径只能靠平时写代码时多留意。我建议你在考前把《C程序设计语言KR》里的指针章节和结构体章节重新精读一遍特别是“指针与数组的关系”那一节每次读都可能有新收获。3. 数据结构与算法题的实战拆解3.1 排序与TopK考场手写快排的边界细节排序算法在爱奇艺C方向笔试里通常不是单独一道大题而是作为工具方法内嵌在更大题目中。比如给你一个无序数组找出第K大的数或者给两个有序数组合并去重。但正因为太常用了手写一次快排的能力几乎是默认要求。我建议所有准备校招的同学把快排代码背到“肌肉记忆”的程度。不是为了炫技而是它思路简单但边界条件极多很容易写错。标准写法void quickSort(int* arr, int left, int right) { if (left right) return; int i left, j right; int pivot arr[(left right) / 2]; // 取中间值避免有序数组退化 while (i j) { while (arr[i] pivot) i; while (arr[j] pivot) j--; if (i j) { int tmp arr[i]; arr[i] arr[j]; arr[j] tmp; i; j--; } } quickSort(arr, left, j); quickSort(arr, i, right); }注意这个写法里用的是而不是这是最经典的“挖坑”点。如果用当pivot恰好是数组中重复值时会无限递归或者错序。另一个坑是递归边界要写成left right而不是因为数组可能为空。这些细节没有实际跑过几十遍的人很难一次写对。很多记性好、思维快的同学会用std::sort三秒钟解决问题但笔试系统如果明确要求C语言而不是C那就只能用C函数库里的qsort。qsort的比较函数签名是int (*compar)(const void *, const void *)注意这里要用const void*不能直接写int*我见过太多人在这个细节上编译失败int cmp(const void* a, const void* b) { return (*(int*)a) - (*(int*)b); }TopK问题也是爱奇艺笔试的爱考方向尤其是“从10万个整数中找出最大的K个数”。最优解是维护一个大小为K的最小堆堆顶始终是当前已遍历元素中最大的K个数里最小的那个遍历完整个数组后堆里的K个元素就是答案。在C语言里手写一个堆并不复杂但笔试限时情况下可以退而求其次用快排的partition思路——每次partition后枢轴位置就是最终位置比较pos和K的大小关系决定继续处理左半部分还是右半部分期望时间复杂度是O(n)。3.2 链表与二叉树高频手撕题的代码习惯链表和二叉树是C方向笔试编程题的第二道常客因为考察的是指针操作功底——C语言里操作链表最灵活也最容易写出内存错误。链表反转是绝对的“必背题”。迭代版思路很清晰三个指针pre指向已反转部分头节点cur指向当前待处理节点next暂存下一个节点struct ListNode* reverseList(struct ListNode* head) { struct ListNode* pre NULL; struct ListNode* cur head; while (cur ! NULL) { struct ListNode* next cur-next; cur-next pre; pre cur; cur next; } return pre; }这道题有两个注意点。第一struct ListNode* next cur-next;必须在修改cur-next之前保存否则就找不回原链表的下一个节点了。第二返回值是新的头节点pre而不是原来的head。很多同学在考试中把返回写错导致部分测试用例过不了。链表中还有一个高频题“判断链表是否有环”解法是快慢指针快指针每次走两步慢指针每次走一步如果相遇说明有环。这个算法本身不难但后续追问“如何找到环入口”会让很多人卡住。数学推导是设头节点到环入口距离为a环入口到相遇点距离为b环长为L则相遇时慢指针走了ab快指针走了2(ab)同时快指针比慢指针多走了一圈nL。所以ab nL即a nL - b。也就是说用两个指针分别从头节点和相遇点出发每次都走一步它们会在环入口处相遇。理解了这个推导面试追问环节才不会慌。二叉树题目里层序遍历是笔试高频题。C语言手写层序遍历需要借助队列数组模拟即可struct TreeNode** queue (struct TreeNode**)malloc(sizeof(struct TreeNode*) * MAXN); int head 0, tail 0; queue[tail] root; while (head tail) { struct TreeNode* node queue[head]; printf(%d , node-val); if (node-left) queue[tail] node-left; if (node-right) queue[tail] node-right; }很多题目要求“按层输出”也就是每层单独一行。这时需要记录当前层的节点数量while (head tail) { int levelSize tail - head; for (int i 0; i levelSize; i) { struct TreeNode* node queue[head]; // 输出或加入结果集 if (node-left) queue[tail] node-left; if (node-right) queue[tail] node-right; } }层序遍历的本质是BFS理解了队列这个载体不管是二叉树还是N叉树、图的最短路径都能用同一套思维去解。3.3 图论与动态规划Dijkstra和DP的考场思路图论的Dijkstra算法在爱奇艺这类公司的笔试里出现的频率高于一般互联网公司。为什么因为视频调度、CDN节点选择、网络延迟计算这些真实业务场景都涉及最短路径问题。我在开发网络调度模块时就经常要在几百个节点之间做路径计算Dijkstra是基本功。Dijkstra的思路一句话概括每次从未确定的节点中选出距离起始点最近的那个用它去更新它的邻居节点到起始点的距离。C语言实现通常用邻接矩阵#define INF 0x3f3f3f3f void dijkstra(int graph[][MAXN], int n, int src, int dist[]) { int visited[MAXN] {0}; for (int i 0; i n; i) dist[i] INF; dist[src] 0; for (int i 0; i n - 1; i) { int u -1, minDist INF; for (int j 0; j n; j) { if (!visited[j] dist[j] minDist) { u j; minDist dist[j]; } } if (u -1) break; // 剩余节点不可达 visited[u] 1; for (int v 0; v n; v) { if (!visited[v] graph[u][v] ! INF dist[u] graph[u][v] dist[v]) { dist[v] dist[u] graph[u][v]; } } } }考场提示这里如果用memset(dist, 0x3f, sizeof(dist))初始化INF0x3f3f3f3f的值是1061109567大约10.6亿小于int最大值且两个INF相加不会溢出int。用0x3f3f3f3f而不是常见的0x7fffffff就是为了防止dist[u] graph[u][v]时int溢出变成负数。这种工程细节面试官在看你代码注释时是会加分的。动态规划也是必考虽然2020年爱奇艺第一场不一定出了DP大题但备考必须覆盖。最常考的三个题型是最长公共子序列LCS、最长递增子序列LIS、01背包。LCS的递推公式是教科书级的dp[i][j] dp[i-1][j-1] 1 当 s1[i-1] s2[j-1]dp[i][j] max(dp[i-1][j], dp[i][j-1]) 当不等代码模板int longestCommonSubsequence(char* text1, char* text2) { int m strlen(text1), n strlen(text2); int dp[m1][n1]; memset(dp, 0, sizeof(dp)); 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] dp[i-1][j] dp[i][j-1] ? dp[i-1][j] : dp[i][j-1]; } } return dp[m][n]; }这里容易搞混的点是dp数组的索引。dp[i][j]表示text1前i个字符和text2前j个字符的LCS长度所以比较的是text1[i-1]和text2[j-1]而不是text1[i]和text2[j]。多写几次就熟了但考场上一紧张很容易在这里错位。01背包的一个通用优化要点是状态转移可以只用一个一维数组但第二层循环必须从大到小遍历容量防止一个物品被重复放入for (int i 0; i n; i) { for (int j W; j weight[i]; j--) { dp[j] max(dp[j], dp[j - weight[i]] value[i]); } }如果第二层是正序遍历dp[j - weight[i]]已经被当前物品更新过了就会造成同一物品被放多次。这是理解01背包退化到一维DP时最常见的错误笔试选择题里也经常拿这个来挖坑。4. 选择题里的计算机基础操作系统、网络、数据库考点4.1 操作系统进程线程、内存、死锁的高频考点操作系统是C方向笔试选择题中占比最大的一块通常能占到选择题的一半左右。因为C/C程序员写的就是贴近操作系统的代码进程管理、内存分配、文件操作都离不开系统调用考这些知识非常合理。进程和线程的区别是必考题。最经典的问法是以下关于进程和线程的描述哪个是错的这里要记牢几个关键差异进程是资源分配的基本单位线程是CPU调度的基本单位同一进程的多个线程共享地址空间进程之间地址空间隔离进程切换开销比线程切换大因为要切换页表、刷新TLB。此外进程至少有三种基本状态运行、就绪、阻塞。有题目会问“一个进程从运行态变为阻塞态的原因”答案是等待I/O完成或等待事件发生而不是时间片用完——时间片用完是从运行态变为就绪态。死锁是另一个高频考点。四个必要条件要能默写互斥、请求与保持、不可剥夺、循环等待。对应的四种处理策略也要能对上预防破坏四个条件之一、避免银行家算法、检测资源分配图、解除终止进程或资源剥夺。选择题特别喜欢混着考比如给出一个场景让你判断它破坏了哪个条件你要能准确对应。虚拟内存和页面置换算法也是高频区。LRU最近最久未使用是考得最多的页面置换算法最近几年爱奇艺这类的笔试还会往工程方向靠比如问“LRU可以用什么数据结构实现”答案是哈希表加双向链表。这个知识点其实也是在暗示你笔试或面试的手撕题可能会考“手写LRU Cache”我确实见过不少公司把LRU Cache当成编程题来出。4.2 网络与数据库概念类题目的快速复习法计算机网络在选择题里一般考得比较基础常见的有TCP的三次握手和四次挥手、TCP与UDP的区别、HTTP状态码含义、DNS解析过程。对于C方向来说网络题的难度通常不会超过这些。TCP三次握手是最常考的关键词是SYN、SYN-ACK、ACK。有时会逆向考为什么是三次而不是两次核心原因是防止已失效的连接请求报文段突然又传到服务器导致服务器建立错误连接。如果只有两次握手服务器在收到一个失效的SYN后就会分配资源客户端却不会按预期响应白白浪费服务器资源。HTTP状态码至少要把下面这几个记牢状态码含义200 OK请求成功301 Moved Permanently永久重定向302 Found临时重定向304 Not Modified资源未修改可使用缓存403 Forbidden服务器拒绝请求404 Not Found请求资源不存在500 Internal Server Error服务器内部错误502 Bad Gateway网关或代理服务器收到无效响应503 Service Unavailable服务器暂时无法处理请求数据库在C方向笔试里考得不多一般就是SQL语法基础、索引类型聚簇索引和非聚簇索引的区别、事务ACID特性、事务隔离级别。重点记一下四种隔离级别对应的并发问题读未提交脏读、读已提交不可重复读、可重复读幻读、串行化全部解决。MySQL默认的隔离级别是可重复读这个细节经常被拿来出题。注意这部分如果时间太紧优先抓TCP/UDP、进程线程、死锁、ACID这些最基础的。网络上关于这些知识点的总结很多但建议还是要结合教材或经典网课去理解死记硬背的答案换个问法就容易错。5. 备考路线、刷题顺序与考场实战策略5.1 给校招生的刷题顺序和资料建议如果你距离笔试还有两个月以上我建议按这个顺序来准备第一阶段1到2周把C语言底层基础补齐。目标是看到任何指针题都能快速分析内存状态。推荐细读KR《C程序设计语言》前七章配合做书后练习题。同时把结构体对齐、位域、宏定义、static关键字的作用域和生命周期搞清楚。第二阶段2到4周刷数据结构常规题。从数组、字符串、链表、栈、队列开始再到二叉树、堆、哈希表最后是图的基本遍历。每一类都要自己动手实现不要只看题解。输出是最好的学习方式用C语言手写一遍链表反转、二叉树遍历比看十遍讲解都有用。第三阶段4到8周刷LeetCode hot 100和剑指Offer。这个阶段的目标不是“全会”而是建立题型思维看到题能快速判断是“双指针”“滑动窗口”“动态规划”还是“贪心”。同时要有意识地在C语言环境下做题不要依赖C的STL这样笔试时才能得心应手。资料方面除了上面提到的KR和剑指Offer我强烈推荐一份叫《C语言常见笔试题》的老资料网上能搜到虽然年份很久了但里面的指针、内存、字符串陷阱题非常经典。不要嫌它老C语言考点这么多年基本没变过。5.2 考场时间分配、编译调试和心态管理先说时间。选择题控制在30到40分钟内完成哪怕遇到不会的也先选一个最可能的并做个标记。编程题先花2到3分钟读题、确认输入输出格式然后直接写暴力解确保至少能过基础用例。如果暴力解超时再想优化。不要一上来就想最优解那是竞赛选手的节奏校招PK的是正确率和稳定性不是炫技。在线笔试的编译环境需要注意通常支持C11或C14你不需要自己include多余的头文件但要记住标准库函数名。比如strdup不是C标准函数在严格C11环境下可能编译失败最好自己实现或用malloc加strcpy。如果你不确定某个函数是否存在就别用写个简单循环替代。调试技巧方面利用好打印调试法。笔试系统不会给你打断点的机会但你可以临时输出中间结果来定位问题。注意提交前必须把调试输出删掉否则多余的输出会导致Wrong Answer。我见过太多人死在调试输出上——逻辑全对但输出格式多了一行判题系统直接判错。这里分享一个我自己的爆发心态技巧遇到不会的题先往后跳做完所有能拿分的题再回头啃。笔试只看总分不看你哪道题做得快。保证基础题全对比强行解出难题更重要。这个策略听着简单但真正上考场能做到的人不多。另外笔试前务必提前登录牛客网模拟一次在线编程环境熟悉那种“没有IDE提示、没有编译器语法高亮、代码全靠手敲”的感觉。C语言是个对拼写和分号极其敏感的语言少写一个分号、变量名大小写不一致在本地IDE里可能能靠自动补全躲过去在网页编辑器里就是编译错误白白浪费调试时间。5.3 笔试后到面试前的衔接从做题到讲思路笔试结束并不意味着可以彻底放松。就我的经验来看爱奇艺的面试官在面试现场会拿到你笔试时的答题记录他会有意问你笔试中的某道题考察你是不是真的理解了还是背答案背出来的。所以笔试结束后趁记忆热乎赶紧把你当时没做出来的题、做出来但觉得不优雅的题重新做一遍整理出清晰的解题思路能讲给别人听那种。面试时讲题和笔试时刷题是两套逻辑。笔试考察你“能不能写出来”面试考察你“能不能说明白”。我建议你按“思路推导—复杂度分析—边界条件—优化方向”这个顺序来准备每题的说辞。比如笔试考了快排你要能说出“最坏情况是O(n²)如何避免退化随机选pivot或三数取中工程上实际用的是Introsort结合堆排和快排”这种深度才算真正及格。C方向的同学还要额外准备一个环节项目里的C/C实战经历。哪怕只是课程设计、实验室项目、开源贡献面试官都会追问内存管理、并发控制、调试工具gdb、valgrind的使用。笔试只是门票面试才是决定offer的战场但这张门票必须拿稳。把笔试当成一次全面的自我体检——每一道错题都是在帮你在面试前补上一个漏洞。这比多做十套题都有用。
返回列表