ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

360校招笔试真题解析:从C语言到算法,研发岗硬核考点全梳理

360校招笔试真题解析:从C语言到算法,研发岗硬核考点全梳理 2015年那会儿互联网公司校招最火的是BAT但360的笔试一直被大家私下称为“硬核代名词”。原因很简单它的研发在线笔试题不跟你玩虚的选择题直接考C语言指针、位运算编程题上来就是手写链表和二叉树后面还跟着几道跟安全相关的系统题。我当时刷这套题最大的感受是——它不像是在筛“背过多少知识点的人”而是在筛“真正能写代码的人”。这套2015年的题哪怕放到现在也一点不过时。对于正在准备校招的研发岗同学来说它是一份很好的“题型样本”能帮你摸清互联网公司在线笔试的出题套路对于已经工作几年的开发者它也是一份不错的“技术体检表”很多基础考点你在日常业务代码里未必碰得到但面试时一定会被问到。这篇文章我会按试卷结构拆开讲把每类题的考察逻辑、解题思路、容易踩的坑都拉一遍重点编程题会附上可直接跑的C语言实现最后再聊聊在线笔试的时间分配和复盘方法。1. 试卷拆解题型分布和出题意图这套笔试题虽然是线上作答但结构上跟线下纸质卷子差别不大主要分三大块单选/多选客观题、逻辑与智力题、编程题。时间大概90分钟总分100分。根据当年的考情我按回忆和多方对题还原了下面这个大致分布。1.1 基础客观题覆盖C/C、数据结构、操作系统与网络客观题大概占了40到50分一般在15到20道之间包括单选和多选。考察范围集中在下面几个方向C/C语言细节指针、数组、内存布局、关键字作用、sizeof与strlen的区别、宏定义陷阱数据结构基础时间复杂度与空间复杂度计算、栈和队列特性、二叉树遍历、排序算法稳定性操作系统进程与线程区别、死锁条件、内存管理、系统调用与库函数的区别网络基础TCP三次握手、TCP与UDP区别、HTTP协议、DNS解析流程安全方向360的特色题XSS、SQL注入、缓冲区溢出、常见加密算法的基本概念我当年做下来感觉C/C题占比最高差不多能到六成。这跟360早期以PC安全产品起家有很大关系客户端研发需要大量跟内存、指针打交道所以C/C功底必须扎实。选择题里最喜欢考的其实是“细节”和“边界”比如“这段代码运行结果是什么”“这个表达式的值是多少”不会直接问你概念定义而是让你在具体例子里判断。这就要求你平时写代码时不是只管功能跑通还要真的明白底层发生了什么。1.2 逻辑与智力题考察思维方式和临场反应这部分大概占10到15分题量不多一般是2到4道。常见题型有数字推理、图形推理、逻辑真假话、概率题、最优策略题。比如经典的“有1000瓶酒其中一瓶有毒用最少多少只小白鼠能找出毒酒”这种题就属于典型的二进制编码思路。这种题跟你的技术栈无关纯粹考脑子转得快不快。说实话这类题短期突击效果很差平时不做逻辑题的人现场很容易卡住。我的建议是不要把时间耗在上面。在线笔试时间紧如果一道逻辑题想了两分钟还没有明确思路直接标记跳过先把后面稳拿分的编程题做了再说。逻辑题分数占比不高为它牺牲一道编程题完全不值得。1.3 编程题在线OJ形式核心考察代码落地能力编程题一般2到3道每道15到20分总分大概40到50分。这是整张卷子的重头戏也是拉分的关键。2015年的在线笔试平台已经能自动判题了要求你写出完整可运行的代码不仅要思路对还要能通过测试用例。编程题考察的方向其实很集中字符串处理、链表、二叉树、排序、二分查找、简单的动态规划。难度从“会写Hello World就能做”到“刷过一定题量才能做”都有分布。第一道通常是热身题比如反转字符串、判断回文数这种第二道开始上强度可能要你实现完整的链表操作第三道往往是一道标准的中等难度算法题比如最长公共子序列、快速排序手写、二叉树层序遍历等。这里有一条很重要的经验笔试时写代码一定要先在纸上或编辑器里理清思路再动手。在线OJ环境没有IDE那么友好的调试功能写完基本就是一遍过你根本没有机会反复编译调试。所以在提交前花一分钟把边界条件、空值判断、大数情况过一遍比盲目抢时间重要得多。2. 编程题实战字符串、链表、排序怎么拿满分编程题是整个笔试里性价比最高的部分——只要你思路对了、代码没写崩分就到手了。这一节我把当年最常考的几类题逐一拆开给出实现代码和容易忽略的细节。不管你现在用什么语言把这些底层的实现逻辑吃透了换语言只是语法层面的翻译。2.1 字符串反转入门题里最容易丢分的点字符串反转看起来简单但笔试里至少有三成的人会在细节上翻车。C语言版本的经典写法是双指针原地反转#include stdio.h #include string.h void reverse(char *s) { if (s NULL) return; int left 0; int right strlen(s) - 1; while (left right) { char tmp s[left]; s[left] s[right]; s[right] tmp; left; right--; } } int main() { char str[] hello; reverse(str); printf(%s\n, str); return 0; }有几个坑必须注意。第一函数入参一定要判空笔试测试用例确实会传NULL进来不判空直接段错误一道题分数全没。第二strlen返回的是size_t是无符号类型如果字符串为空strlen(s) - 1会变成一个巨大的正数进入循环后直接数组越界。所以写int right (int)strlen(s) - 1;或者提前判断字符串长度是否小于等于1都是稳妥的做法。第三题目如果要求“原地反转”你就不要额外开一个字符数组否则空间复杂度不达标。除了双指针还有递归写法但不推荐因为递归会占用栈空间字符串长一点容易爆。我在实际笔试中见过有人用递归写测试用例一长就Stack Overflow非常可惜。字符串反转这类题真正要练的是“对边界条件的敏感度”而不是花哨解法。2.2 二分查找看似简单实则处处是陷阱二分查找是笔试高频题但能一次写对的人真不多。问题主要出在循环条件和中间值计算上。直接给一个稳妥模板int binary_search(int arr[], int n, int target) { int left 0; int right n - 1; while (left right) { int mid left (right - left) / 2; if (arr[mid] target) { return mid; } else if (arr[mid] target) { left mid 1; } else { right mid - 1; } } return -1; }这个写法最需要注意的点有三个。一是while (left right)还是while (left right)。用配合right n - 1是闭区间写法每次缩小范围时mid已经比较过了所以left和right都要跳过mid。如果用退出的条件就变了返回值和后续逻辑都要跟着调新手容易搞混所以我建议平时就固定用一种写法。二是mid的计算要用left (right - left) / 2不要直接写(left right) / 2。在数组特别大时left right可能超出int范围造成溢出用减法就不会有这个问题。这属于笔试中“看起来没毛病数据一大就出错”的典型问题。三是找不到目标值时返回什么。一般返回-1但如果题目要求插入点比如“找到第一个大于等于target的位置”那返回left就行。这个变体在笔试里更常见练的时候把“找精确值”和“找边界值”两种都写熟能覆盖大部分二分题。2.3 链表操作反转链表与快慢指针链表是笔试常客因为它能同时考察指针操作、内存理解和代码组织能力。链表反转是经典中的经典struct ListNode { int val; struct ListNode *next; }; struct ListNode* reverseList(struct ListNode* head) { struct ListNode *prev NULL; struct ListNode *curr head; while (curr ! NULL) { struct ListNode *next curr-next; curr-next prev; prev curr; curr next; } return prev; }这里最关键的是保存curr-next。很多人写反转链表第一反应是直接改当前节点的next指向前一个节点但改完之后原来的下一个节点就找不到了所以必须在改动前先把next存下来。这个失误非常典型几乎每个初学链表的人都会踩一次。除了反转还有一道高频链表题是“判断链表中是否有环”。常规思路是快慢指针快指针每次走两步慢指针每次走一步如果两者相遇说明有环如果快指针走到空节点说明无环。int hasCycle(struct ListNode *head) { if (head NULL || head-next NULL) { return 0; } struct ListNode *slow head; struct ListNode *fast head-next; while (slow ! fast) { if (fast NULL || fast-next NULL) { return 0; } slow slow-next; fast fast-next-next; } return 1; }为什么快指针走两步、慢指针走一步就能判断出环可以想象两个人在环形跑道上跑步速度不同慢的最终一定会被快的“套圈”于是两人相遇。如果不在环上快的会先到达终点也就是空节点。这个题在笔试里不光是问判断有没有环有时候还会追问“环的入口在哪”那就需要用到相遇点到环入口的距离等于头节点到环入口的距离这个数学结论原理不复杂但推导一遍能加深理解。链表题的共同技巧是动手写节点操作之前先在草稿纸上画出节点的指向变化指针改来改去很容易晕画出来就清晰多了。2.4 手写快速排序与优化点排序算法在选择题里经常考复杂度和稳定性在编程题里则可能让你手写一个快速排序。快速排序的核心是分治选一个基准值把数组分成小于等于基准和大于等于基准两部分再递归排序。void quick_sort(int arr[], int left, int right) { if (left right) { return; } int pivot arr[left (right - left) / 2]; int i left; int j right; 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--; } } quick_sort(arr, left, j); quick_sort(arr, i, right); }这个写法的好处是基准值取中间位置能在很大程度上避免近似有序数组的退化情况。快速排序的最坏时间复杂度是O(n^2)发生在每次分区严重不平衡时比如数组本身已经有序又每次都取第一个元素作为基准。解决办法就是随机取基准或者取中位数作为基准。笔试中手写排序基本不会有人要求你写出稳定的归并排序或者堆排序除非题目明确要求“稳定排序”。所以快排要写得滚瓜烂熟同时要能说出来它的时间复杂度平均是O(n log n)、最坏是O(n^2)、空间复杂度是O(log n)到O(n)。这些“附加题”经常出现在面试官追问环节。3. 算法题深挖二叉树、动态规划与海量数据第三道编程题往往不再是“背模板能解决”的级别需要一点算法思维。2015年这个时间点在线笔试难度已经上来了二叉树遍历、动态规划、海量数据TopK都是常客。3.1 二叉树遍历递归易写迭代才是分水岭二叉树的前序、中序、后序和层序遍历是算法题基础。递归版好写但笔试有时会故意限制要求用非递归实现因为递归的本质是使用了系统栈你无法控制栈的增长数据量大时有溢出风险。以中序遍历为例非递归需要显式维护一个栈struct TreeNode { int val; struct TreeNode *left; struct TreeNode *right; }; void inorderTraversal(struct TreeNode* root) { if (root NULL) { return; } struct TreeNode* stack[1000]; int top -1; struct TreeNode* curr root; while (curr ! NULL || top 0) { while (curr ! NULL) { stack[top] curr; curr curr-left; } if (top 0) { curr stack[top--]; printf(%d , curr-val); curr curr-right; } } }这段代码的核心思路是一直往左走把路过的节点都压栈走到最左边后弹出节点访问再转向右子树。理解了这个过程前序和后序的迭代写法也能顺带推出来。层序遍历则需要用队列。思路是先访问根节点然后把它的左子节点和右子节点依次入队再依次出队访问同时把每个出队节点的子节点入队。这个过程天然契合“广度优先”的语义。层序遍历在笔试里常被包装成“按层输出二叉树”或者“之字形打印二叉树”其实就是层序基础上加一个逻辑判断。二叉树题的通用技巧是先想清楚“访问节点”这个动作发生在遍历的什么时机。前序是第一次见到节点就访问中序是左子树访问完再访问后序是左右子树都访问完再访问。把这个时机和递归写法对应上理解起来会非常顺。3.2 动态规划最长公共子序列LCS的完整推导动态规划是很多人的老大难但其实笔试里翻来覆去就那么几种经典模型最长公共子序列、最长递增子序列、背包问题、编辑距离。把一种模型真正吃透其他都是套模板。以最长公共子序列为例经典题目是给定两个字符串A和B求它们的最长公共子序列长度。子序列不要求在原字符串中连续只要保持相对顺序一致。状态定义dp[i][j]表示A的前i个字符和B的前j个字符的最长公共子序列长度。状态转移如果A[i-1] B[j-1]那么dp[i][j] dp[i-1][j-1] 1如果A[i-1] ! B[j-1]那么dp[i][j] max(dp[i-1][j], dp[i][j-1])初始化dp[0][j] 0dp[i][0] 0因为空字符串和任意字符串的公共子序列长度为0。用C语言实现int lcs(char *a, int m, char *b, int n) { int dp[m 1][n 1]; for (int i 0; i m; i) { for (int j 0; j n; j) { if (i 0 || j 0) { dp[i][j] 0; } else if (a[i - 1] b[j - 1]) { dp[i][j] dp[i - 1][j - 1] 1; } else { int t1 dp[i - 1][j]; int t2 dp[i][j - 1]; dp[i][j] t1 t2 ? t1 : t2; } } } return dp[m][n]; }动态规划题最核心的难点不是写代码而是定义状态和推状态转移方程。我个人的方法是拿到题先不要急着写找个小例子在纸上把dp表画出来手动填一遍。比如AabcdeBace把5x3的表格填完公式其实自己就浮出来了。这种题笔试时宁可多花五分钟推演不要直接硬写写错了调试更浪费时间。LCS的应用场景也很多比如文本diff、代码相似度比较、基因序列比对。在360这类公司文本分析和数据挖掘项目里也经常用到所以把这道题吃透对实际工作也有帮助。3.3 海量数据与TopK考验工程思维的题海量数据处理在选择题和简答题里都很常见尤其是“内存不够怎么办”这类问题。有一道经典题从100亿个整数中找出最大的100个数请问怎么做。最简单的思路是全排序后取前100时间复杂度O(n log n)但100亿个数不可能一次性加载到内存。所以更合理的方案是维护一个大小为100的最小堆。遍历数据时如果当前元素比堆顶大就把堆顶移除插入当前元素否则直接跳过。这样遍历完所有数据后堆里的100个元素就是最大的100个。最小堆的插入和删除复杂度都是O(log k)其中k100整体复杂度是O(n log k)远小于全排序。这个题还有另一个方案如果允许内存足够用快速排序的partition思想每次把数据分成“大于基准”和“小于基准”两部分只保留包含Top100的那一半继续递归。这种做法的平均时间复杂度是O(n)比堆排序法还快但要求数据能在内存中放下。所以到时候要先跟面试官或题目确认数据量级和内存限制再给出对应方案。TopK这个题型在当年的笔试里可能只是一个思路题但到了实际工作中非常常见日志里找访问量最高的URL、搜索词里找热搜榜、推荐系统里找TopN商品。把最小堆和partition两个思路都练熟工作时能少走很多弯路。4. 系统基础题操作系统、网络和安全重点客观题里占比第三大的是系统基础知识。这部分突击效果最明显因为考点非常固定。360又有安全基因所以跟安全相关的题尤其值得重视。4.1 进程与线程、死锁选择题最爱考的固定套路进程与线程的题目一般围绕下面几个点进程是资源分配的基本单位线程是CPU调度的基本单位进程地址空间独立线程共享进程的地址空间进程切换开销大于线程切换进程间通信方式管道、消息队列、共享内存、信号量、Socket等线程间通信主要通过共享内存和锁机制死锁是真正的重灾区几乎每年必考。死锁产生的四个必要条件互斥、持有并等待、不可剥夺、循环等待。选择题经常这样问“下列哪些方法可以预防死锁”答案通常就是在说破坏这四个条件中的哪一个。比如“一次性申请所有资源”是破坏“持有并等待”“资源按序分配”是破坏“循环等待”“允许抢占”是破坏“不可剥夺”。记住四个条件再逐个选项对号入座这类题就拿下了。还有一个高频考点是银行家算法它属于避免死锁的方法。笔试一般不会让你完整实现但是会给一个资源分配矩阵让你判断某个状态是否安全。做法是先找“当前剩余资源能满足需求”的进程让它在有限时间内运行完并释放资源然后看剩余进程能不能按这个流程全部完成。能全部完成就是安全状态否则就是不安全状态。这个判断流程练两遍就会了但有一个细节容易出错计算剩余资源时要把已经运行完的进程释放的资源加回来。4.2 TCP三次握手与TIME_WAIT网络题的标准答案TCP/UDP题也是必考最常见的就是三次握手的过程。这个要熟到什么程度就是能画出连接建立的步骤并知道每一步的报文标志位是什么客户端发送SYN报文等待服务器确认服务器收到SYN回复SYNACK报文表示“我收到了你的请求同时我也要建立连接”客户端收到SYNACK再发送ACK报文连接建立为什么要三次握手而不是两次最简单的解释三次握手能让双方都确认“自己发送和接收的能力都没问题”。第二次握手时服务器既收到了客户端的SYN又回复了自己的SYN所以服务器确认了“客户端能发、自己能收、自己能发”但还不能确认“客户端能收”。直到收到第三次的ACK服务器才确认客户端能收连接才真正可靠。四次挥手的过程也常考尤其喜欢问TIME_WAIT。主动关闭连接的一方在发送最后一个ACK后会进入TIME_WAIT状态持续2MSL两个最大报文段生存时间。为什么要等这么长时间因为要确保最后一个ACK能被对方收到。如果这个ACK丢了对方会重发FIN主动方需要能再次响应。另外还要让旧的报文在网络中自然消失避免影响后续新的连接。表格整理TCP状态变化记起来更方便阶段主动方状态被动方状态建立连接前CLOSED - SYN_SENTLISTEN - SYN_RCVD三次握手完成ESTABLISHEDESTABLISHED关闭连接FIN_WAIT_1 - FIN_WAIT_2CLOSE_WAIT关闭连接后TIME_WAIT - CLOSEDLAST_ACK - CLOSED这种表格不是让死记硬背的而是建议自己用抓包工具实际看一次握手挥手过程Windows上用Wireshark命令行的用tcpdump观察一遍就全记住了比死记牢固得多。4.3 安全基础题360公司笔试区别于其他厂的特色这部分是我觉得最有360“厂味”的题目。一般互联网公司考网络基础到TCP/UDP就差不多了但360会接着往下问安全方向的问题。我当时遇到的几个知识点后来在工作中也在反复接触到。第一个是缓冲区溢出。简单说就是程序往固定长度的缓冲区写入超出容量的数据多出来的数据会覆盖相邻内存区域攻击者可以利用这一点改写返回地址让程序跳转到恶意代码。防御手段有栈保护机制canary、地址空间随机化ASLR、不可执行栈等。这类题考的是对内存布局的理解跟C语言指针题是联动的。第二个是SQL注入。攻击者在输入框或URL参数中嵌入SQL代码让后台拼接SQL语句时执行攻击者可控的逻辑。比如登录处输入 OR 11如果后台直接拼SQL就可能绕过密码验证。防御方法是参数化查询和预编译语句而不是拼接字符串。第三个是XSS跨站脚本攻击。攻击者将JavaScript代码注入页面其他用户访问时执行恶意脚本可以窃取Cookie、篡改页面内容等。防御方法是输入过滤和输出转义。考试时经常给一段代码问你哪里存在XSS漏洞、应该怎么修。第四个是CSRF跨站请求伪造。攻击者诱导用户访问恶意页面该页面偷偷向用户已登录的网站发起请求。因为浏览器会自动携带Cookie服务端无法分辨这是用户本人操作还是攻击者伪造的操作。防御手段是加入CSRF Token、校验Referer字段、使用自定义Header等。如果你本身没有安全背景考前把上面这几个常见漏洞原理、攻击方式、防御手段各整理出三句话背熟基本就能应付选择题。如果你想往安全方向深入建议再了解一下对称加密AES和非对称加密RSA的区别、数字签名与证书的关系这些也常出现在加试题里。5. 复盘与备战时间分配、易错点和刷题方法笔试不只是考察你会不会还考察你在有限时间里怎么分配精力。我当年第一次参加在线笔试时栽过跟头在选择题上死磕一道没思路的指针题结果最后编程题只写了一半就交卷了。后来总结经验调整了策略效果好了很多。5.1 在线笔试的时间分配方案如果是90分钟完成20道客观题加3道编程题我推荐的分配方式是客观题30分钟编程题50分钟最后10分钟检查。具体到每一道编程题先花2到3分钟读题和想清思路再花10到15分钟写代码写完检查边界条件。如果一道编程题超过20分钟还没写出来果断放弃去保另一道更简单的题。拿到卷子第一件事是先扫一眼所有编程题。不要按顺序从第一道开始硬写先看哪道题最熟悉、最容易拿满分先做那道。我见过很多人一上来就死磕第二道算法题结果第一道简单的字符串题反而没时间写。这个策略放在任何考试里都适用先拿稳分再冲高分。时间分配表可以参考卷面部分建议用时策略选择题10-15分钟会的秒选不会的标记跳过逻辑题5-10分钟最多想2分钟不行放掉编程第一题15分钟保正确率边界条件全测编程第二题20分钟尽力拿全分编程第三题20-25分钟有思路就写没思路写暴力解检查5-10分钟重点看输入输出格式、空值判断提示在线笔试的编程题往往要求“不要输出多余字符”很多人代码逻辑对了但多打印了一个提示语句导致判题失败。提交前务必确认输出格式和题目要求一模一样。5.2 高频易错点清单考前对照自查我把这些年在校招笔试和面试辅导中遇到的最高频易错点整理成了一份自查清单每一届学生笔试前我都会发给他们过一遍sizeof和strlen的区别sizeof是编译期运算符strlen是运行期函数。sizeof(abc)是4strlen(abc)是3指针数组和数组指针int *p[10]是指针数组int (*p)[10]是数组指针静态变量和全局变量的初始化未显式初始化时会被默认置为0但局部普通变量不会死循环问题for (i 0; i n; i)里循环变量类型用unsigned int当n0时可能出现死循环因为i减到0后再减会变成很大的正数快排是不稳定排序归并排序是稳定排序堆排序也是不稳定排序二分查找边界条件left right或left right对应着不同的更新逻辑写混了就是死循环链表的头节点为空、只有一个节点两种特殊情况二叉树递归遍历时是否判空动态规划数组下标从0开始还是从1开始边界初始化是否正确这份清单看起来都是小细节但笔试翻车十有八九都发生在这些“看起来很简单”的地方。我的建议是笔试前一晚别刷难题了把这份清单过一遍比多做十道题都有用。5.3 笔试后的复盘方法把一套题的价值榨干笔试结束不等于这件事结束了复盘才是真正拉开差距的地方。很多人考完对了答案就扔一边下次遇到同类型题照样不会这就是白考了。我自己的复盘流程是这样笔试结束后当天趁着记忆还热立刻打开笔记记录三件事。一是哪些题卡住了卡在哪个环节是知识点不熟、还是思路没打开、还是代码实现不熟练。二是哪些题是“侥幸做对”的比如选择题蒙对的、编程题碰巧通过的这些知识点必须单独标记出来补强。三是整个时间分配有没有不合理的地方下一次考试要怎么调整。记录完之后针对卡住的题型去刷10道同类题巩固。比如二叉树迭代遍历不会写就去刷前序、中序、后序、层序的迭代实现各两三道直到不再卡壳为止。这一步非常关键因为校招笔试题型就那么几个大方向只要你在一次笔试中补上了短板下次笔试就会明显感觉到自己变强了。另外建议建立一个自己的代码模板库把字符串、链表、二叉树、排序、二分、DP的经典模板都保存下来平时多默写几遍。真正上考场时能快速“肌肉记忆”输出省下来的时间可以用来攻难题。这个做法从2015年到现在我带过的同学都在用效果一直很好。我在实际踩过几次坑之后最大的体会是校招笔试题其实很少考偏题怪题它比的就是一个“基础是否真的扎实”。很多人刷题追求数量觉得刷了三五百道就稳了但真到了考场能被一道简单的字符串反转问倒。反而是那些能把每道经典题背后的边界条件、复杂度分析、工程考量都讲清楚的人无论试卷怎么变都能稳住。这套2015年的360校招题就是一个很好的试金石你现在拿它自测一遍如果大部分题都能秒解那你的研发基础基本就过关了。
返回列表