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

资讯详情

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

猎豹移动2016研发笔试题解析:C++、Java与算法核心考点全拆解

猎豹移动2016研发笔试题解析:C++、Java与算法核心考点全拆解 猎豹移动2016年研发工程师笔试题在当年的校招圈里算是一套口碑很“实”的卷子。说它实是因为这套题不堆偏题怪题而是把研发岗日常真正要用的基础能力铺开来考。当时猎豹移动正处在工具类产品向内容产品转型的阶段Clean Master、猎豹安全大师的用户量增长很快客户端团队对C、Java、Android底层和算法基础的要求都不低这套题基本能反映出一家头部出海移动互联网公司挑研发人员的真实尺度。如果你想投客户端、后端或者算法方向的研发岗拿这套题做一次限时自测非常合适。整套卷子做下来你能比较客观地看出自己在语言底层、数据结构、操作系统、网络这些模块上还有没有短板。下面我会从题目结构、典型考点、完整解题思路和代码实现几个角度做一次系统拆解顺带聊聊面试官到底想从这些题里看到什么。1. 这套笔试题的背景与整体定位猎豹移动当年想要什么样的人1.1 2016年前后的技术栈与招聘偏好2016年的移动互联网公司研发岗位的笔试题目风格还没有现在这么“算法竞赛化”。猎豹移动的题在当年属于典型的中等偏上难度不考ACM级别的复杂模型但基础知识覆盖面很广。从各地考生回忆汇总的情况来看这套卷子主要分为客观题、简答题和编程题三部分重心明显偏向C/C、Java、Android底层和基础算法。为什么会这样考回过头来看猎豹移动的拳头产品都以客户端为主Clean Master涉及文件清理、内存加速、垃圾扫描这些功能对底层系统API的理解要求很高。工程师需要对系统进程、文件系统、Binder通信、内存管理这些概念有扎实的掌握。所以笔试题里出现大量操作系统和Linux相关内容并不让人意外。这套题对应届生的要求其实很明确语言基础要扎实数据结构和算法要能上手写代码计算机网络数据库这些计算机核心课程不能丢。它不像一些大厂只刷算法题就能过基础概念不牢的人客观题阶段就会被刷掉。准备这套题本质上就是在打磨一个研发工程师的基本盘。1.2 一套笔试到底在筛什么人笔试的核心作用不是筛出最聪明的人而是筛掉基础不牢、学习能力一般的人。面试官心里清楚校招生进来都要重新培养但基础差的人培养成本太高。从猎豹移动这套题可以看出几个筛人维度语言底层原理是否清楚。比如C里指针和引用的区别、内存分配方式、Java中HashMap的实现机制这些不是背一背就能过关的需要真正理解底层运行过程。算法功底是否达到平均线以上。重点考察的还是常用数据结构和经典算法比如链表操作、字符串处理、动态规划、二叉树遍历极少出偏题。是否有工程意识。简单题里会夹杂一些容易忽略的边界条件代码是否考虑空指针、数组越界、内存释放这些都能反映一个开发者有没有工程习惯。理解了筛人逻辑复习方向就清晰了把计算机核心基础课吃透把常见的算法题刷熟练不需要追难题怪题。这套题的风格与当时不少中大型互联网公司的笔试题类似如果你能稳定做出其中90%的内容大部分公司的笔试关都能过。2. 基础题怎么答C/C、Java、Android底层高频失分点2.1 sizeof、strlen、指针与引用语言底层的“送分题”其实不送分C/C部分是这套笔试题的重头戏首先绕不开的就是sizeof和strlen的区别。这两个看起来很简单实际错误率非常高。sizeof是运算符编译期就能求值目标是类型或者变量计算的是对象在内存中占用的字节数会包含字符串结尾的\0。strlen是库函数运行期才能执行目标是字符串指针计算的是到第一个\0之前字符的个数不包含结尾的\0。比如char str[] hello; sizeof(str); // 结果是6包括结尾的\0 strlen(str); // 结果是5只统计可见字符笔试题往往会再挖一个坑如果把str作为参数传给另一个函数函数里写下sizeof(str)结果就不再是6了而是指针的大小在64位系统上是8。很多人在这个点上丢分本质是没有区分“数组名”和“指针变量”在sizeof场景下的语义差别。指针和引用的区别同样是高频考点。引用是变量的别名必须在声明时初始化之后不能改变指向指针是一个变量存的是地址可以重新赋值。引用相对于指针更安全省去了判空的步骤但底层实现的机制本质上还是指针。这类题想拿分建议不要死记结论而是动手在Linux下写几个小程序用gdb或者printf观察一下变量地址、大小印象会深很多。笔试考的是理解不是记忆。2.2 HashMap原理与Java内存泄漏高频又容易翻车的点Java方向的题目里HashMap源码级别的问题是标配。2016年常见的问法是HashMap的底层数据结构是什么put一个键值对的过程是怎样的什么时候触发扩容标准的回答路径是这样的HashMap底层是数组加链表Java 8及之后加入了红黑树。put时先对key的hashCode做一次高16位异或低16位的扰动运算然后通过掩码运算得到数组下标。如果该位置为空直接放入如果不为空遍历链表找相同key找到就覆盖找不到就在尾部插入新节点。链表长度超过8且数组长度达到64时链表转红黑树。当已存储元素数量超过容量乘以加载因子0.75时触发扩容容量翻倍。这里还有个容易被追问的点为什么HashMap的容量总是2的n次方因为hash值定位下标用的是(n - 1) hash只有容量是2的n次方时这个掩码运算才能让下标均匀分布且比取模运算性能更高。Java内存泄漏题目也常见。典型场景是Handler持有Activity的引用导致Activity无法被回收或者静态集合持有短生命周期对象的引用。标准解答是讲清楚可达性分析和GC Root然后用静态内部类加WeakReference改造Handler。这类题目直接关联Android开发猎豹移动很爱考因为它能考察候选人有没有真实做过客户端项目。3. 核心算法题拆解从暴力解到最优解的完整思考路径3.1 字符串类题型的答题套路这套笔试的算法题不追求超难但很看重解题的规范性和完备性。字符串题是当年出现频率最高的类型常见的形式包括字符串翻转、判断回文、找出第一个不重复字符、两个字符串比较编辑距离。以字符串翻转为例最基础的解法是用双指针从两端往中间交换字符void reverseString(vectorchar s) { int left 0, right s.size() - 1; while (left right) { swap(s[left], s[right]); left; right--; } }如果题目升级为“按单词翻转”比如the sky is blue变成blue is sky the思路就变成了先整体翻转整个字符串再对每个单词单独翻转。笔试时一定要先跟面试官确认清楚边界条件连续多个空格如何处理、是否区分大小写、是否保留首尾空格。这些细节恰恰是评分点。字符串类题目的通用套路是想清楚数据组织方式能用双指针就用双指针需要频繁查询某个字符是否出现就建一个长度为128或者256的数组当哈希表用避免了HashMap的开箱损耗。向面试官展示你具备复杂度意识比单纯写出一个能跑的结果更重要。3.2 经典动态规划题的思考路径动态规划是检验算法功底的分水岭。这套题里出现的动态规划题通常不会太上难度但要求你写出完整的转移方程和代码实现并且能自测几个用例。做动态规划第一步是明确状态定义。以“给定两个字符串计算编辑距离”为例需要定义dp[i][j]表示把字符串A的前i个字符转换成字符串B的前j个字符所需的最小操作次数。然后处理初始化dp[i][0] i因为要把A的前i个字符全部删除dp[0][j] j因为要把B的前j个字符全部插入。转移方程分情况如果A[i-1]和B[j-1]相等dp[i][j]直接继承dp[i-1][j-1]如果不相等在“插入”“删除”“替换”三种操作中取最小值再加1。面试时建议先举一个具体例子在纸上画表格再抽象出方程。比如“abc”到“adc”中间字符不同一次替换就能完成dp值应该是1。用这个用例验证公式能够尽早发现初始化或者边界是否出错。动态规划的核心不是背题而是建立“状态定义、状态转移、边界初始化、例子验证”这样的思维链路。笔试阅卷看的是你的推到过程是否能自洽。3.3 链表与二叉树高频数据结构题不能失分链表和二叉树题在这套卷子里也有不小的比重。单链表反转是必考中的必考迭代写法要用三个指针pre、cur、next每一步把cur的next指向pre然后三个指针整体后移。很多人递归写法很熟反而迭代写法卡壳所以两种写法都要练。二叉树的主要考点是各种遍历的递归和非递归实现尤其是层序遍历和利用层序遍历的变种题。非递归前序遍历要用栈入栈时先右后左层序遍历要用队列每次处理一整层。笔试里还常考“求二叉树深度”这类递归题注意终止条件要写成空节点返回0很多人写成了返回1导致根节点深度算成了2。链表和二叉树题的核心总结边界条件一定要在写代码前就想好。单链表为空、只有一个节点、二叉树为空树、只有左子树这些情况都要在代码里显式处理。面试官不会只看结果正确性还会看代码健壮性。4. 操作系统、网络与数据库考点盘点一个都不能漏4.1 进程与线程的那些“标准答案”操作系统部分出现频率最高的是进程和线程。笔试现场经常出这样的简答题进程和线程的区别有哪些死锁产生的四个必要条件是什么如何避免死锁进程和线程的完整答案至少包含四个层面从资源分配上看进程是资源分配的基本单位线程是CPU调度的基本单位从地址空间看进程有独立的虚拟地址空间线程共享所属进程的地址空间从通信方式看进程间通信需要管道、消息队列、共享内存、信号量等手段线程间通信则可以直接通过共享全局变量完成但需要加锁来保证同步从系统开销来看进程切换开销大线程切换开销小。死锁部分则要答出互斥、占有并等待、不可剥夺、循环等待四个必要条件并且能针对每个条件说出一个对应的避免方法比如使用资源有序分配法来破坏循环等待条件。光背条件是拿不全分的还要有自己的理解体现为能结合例子说明。建议复习时把这些概念整理成自己的话不要直接背教材原文。笔试阅卷时能看到“从进程和线程的地址空间差异来看……”这种有自己痕迹的表达基本就是高分答案。4.2 select、poll、epoll与TCP三次握手网络考点怎么答才算有深度网络部分考得最频繁的是TCP协议和IO多路复用。猎豹移动这类的客户端公司特别看重网络相关基础因为应用层大量的HTTP请求都跑在TCP之上。TCP三次握手为什么是三次而不是两次这个问题需要答出核心原因防止历史重复连接请求干扰通信。如果只有两次握手服务端无法确认客户端是否收到了自己的SYNACK当客户端早已失效的旧连接请求延迟到达服务端时服务端会误以为客户端想建立新连接从而分配资源却得不到响应造成资源浪费。三次握手让双方都确认了对方的收发能力同时让服务端能通过判断ACK序号来识别并拒绝对历史连接的响应。select、poll、epoll的对比是后端和客户端都会遇到的重点。要从事件驱动方式、最大连接数、消息传递机制和效率几个维度作答select使用fd_set默认有1024个文件描述符的上限每次调用都要把整个fd集合从用户态拷贝到内核态再由内核遍历全部fd来确定哪些就绪poll用链表结构存储fd突破了数量上限但依然需要全量扫描epoll则在内核中使用事件驱动机制注册fd后就只由回调通知就绪事件需要拷贝的就绪列表比全量集合小得多所以高并发场景下优势明显。4.3 数据库索引为什么用B树数据库题在客户端岗的笔试题里占比不高但一旦出现基本就是一道区分度的题目。高频内容是InnoDB的索引为什么用B树而不是B树、不是哈希表、不是跳表。标准答案的核心逻辑是索引设计的核心目的是减少磁盘IO次数。B树只有叶子节点存储数据非叶子节点可以存放更多的键值树的高度因此更矮查找一条记录需要的IO次数更少。相比跳跃表B树的节点是磁盘页大小对齐的磁盘IO更友好相比哈希索引B树支持范围查找和排序而不是只能等值匹配。另外聚簇索引的叶子节点直接存储整行数据非聚簇索引的叶子节点则存储主键值回表这个知识点也要顺带掌握。这类简答题的作答策略是打“组合拳”先回答B树的数据结构特点再对比其他方案最后落到磁盘IO上。这比干巴巴一句话“B树适合范围查询”要充实得多也更容易拿全分。5. 两道编程大题完整手写实现附完整代码和边界分析5.1 题一滑动窗口最大值考察队列与时间复杂度分析题目描述通常是给定一个整数数组有一个大小为k的滑动窗口从数组最左端移动到最右端每次窗口向右移动一个位置输出每个窗口中的最大值。最容易想到的暴力解法是每个窗口都扫描一遍时间复杂度O(nk)数据量大时性能很差。能做到O(n)的方法是维护一个双端队列队列里存的是元素下标并且必须保证队列从队头到队尾对应的数组元素值是单调递减的。这样队头元素始终就是当前窗口的最大值。完整实现如下vectorint maxSlidingWindow(vectorint nums, int k) { dequeint dq; vectorint result; for (int i 0; i (int)nums.size(); i) { // 如果队头元素已经滑出当前窗口移除 while (!dq.empty() dq.front() i - k) { dq.pop_front(); } // 从队尾开始移除所有比当前元素小的元素下标 while (!dq.empty() nums[dq.back()] nums[i]) { dq.pop_back(); } dq.push_back(i); // 窗口形成之后每次移动都把队头元素加入结果 if (i k - 1) { result.push_back(nums[dq.front()]); } } return result; }这里有个容易写错的细节在判断队头元素是否过期时我用的是while循环而不是if因为某些边界场景下窗口前进的速度可能落后于队列中下标落后的速度用循环更稳。另外队尾弹出时要使用而不是因为题目要求的是最大值存在相等元素时保留后出现的下标更合理避免连续相等值时过早淘汰候选元素。面试中写这道题先分析暴力解法的时间复杂度再引出单调队列的思路最后再动笔基本就是标准得分流程。写完后主动补充一句“整体每个元素最多入队出队一次所以时间复杂度为O(n)额外空间是O(k)”这会让面试官觉得你的复杂度意识很到位。5.2 题二字符串编辑距离考察动态规划与滚动数组优化经典的编辑距离题目允许对字符串执行插入、删除、替换三种操作问最少多少次操作可以把字符串A变成字符串B。动态规划的四步法前面已经拆解过这里直接给出可运行的C实现int minDistance(string word1, string word2) { int m word1.size(), n word2.size(); vectorvectorint dp(m 1, vectorint(n 1, 0)); for (int i 0; i m; i) { dp[i][0] i; } for (int j 0; j n; j) { dp[0][j] j; } for (int i 1; i m; i) { for (int j 1; j n; j) { if (word1[i - 1] word2[j - 1]) { dp[i][j] dp[i - 1][j - 1]; } else { dp[i][j] min(min(dp[i - 1][j], dp[i][j - 1]), dp[i - 1][j - 1]) 1; } } } return dp[m][n]; }这里为什么要1因为插入、删除、替换都是一次操作dp[i-1][j]表示删除word1的第i个字符dp[i][j-1]表示在word1中插入一个字符来匹配word2的第j个字符dp[i-1][j-1]表示替换对应字符。三者中取最小再加本次操作的那一步。如果面试官继续追问内存优化可以答出用一维数组配合临时变量来滚动更新将空间复杂度从O(mn)降到O(n)。但这个优化容易让代码清晰度下降笔试阶段优先保证二维做法正确有余力再展开。手写代码的阅卷逻辑很直接能跑通、边界对、复杂度清晰就是满分。6. 复盘这套题笔试筛人逻辑与备考路线6.1 笔试评分到底看什么很多同学以为笔试评分只看结果对不对其实不是。研发岗笔试的阅卷过程通常会综合看几个维度代码是否可读变量命名是否清晰是否考虑了空数组、单个元素、整数溢出等边界条件时间复杂度是否过优有没有做防御性编程比如判断输入是否合法。这些维度的权重在面试官心中往往不亚于最终输出结果。从这套题的经验来看丢分最严重的地方往往不是算法题没写出来而是基础题概念混淆。比如sizeof和strlen分不清、HashMap和HashTable的区别答不上来、进程和线程区别讲不全这些硬伤最容易在简答题环节暴露。算法题写不出来还有思考过程的分数可给概念题答错就是零分。6.2 时间分配与后续面试衔接整套题的题量按当时的标准大概是90到120分钟建议的时间分配原则是客观题不要太恋战单选题和判断题平均每道控制在一分钟以内简答题每题控制在10到15分钟先答要点再展开编程题留足30到40分钟因为不仅要写完代码还要预留时间自测。另外要提醒一点笔试题目和后续技术面的关联度很高。猎豹移动的笔试题有一部分会成为现场面试的起点比如笔试里写了滑动窗口面试官可能会追问它的变种比如无序数组的连续子数组最大和或者从二维矩阵中找最大值。所以笔试结束后要及时复盘自己的思路特别是没做出来的题目面试前一定要弄懂。6.3 按这套题的标准安排复习节奏结合这套题覆盖的知识点准备周期建议分成三个阶段。基础补漏阶段把C/C、Java、操作系统、网络、数据库的核心概念梳理一遍重点整理自己说不清楚的主题刷题强化阶段集中刷链表、二叉树、字符串、动态规划、滑动窗口这类高频题型每道题都要手写并分析复杂度模拟冲刺阶段找几套真题限时模拟练习时间分配和应对突发情况的心态。我个人建议把“笔试题面试化”当作一个练习方法笔试中遇到的每一道错题都尝试用口述的方式把它讲给别人听讲不顺的地方往往就是理解不到位的知识点。这个方法帮我快速发现了操作系统和网络基础中的不少盲区。最后说一点实际体会这套2016年的笔试题放到今天依然很有参考价值。虽然编程语言和框架不断更迭但研发工程师的底层能力考察方向变化不大。认真把这些基础题吃透再去准备任何一家公司的研发岗笔试都会有底气得多。准备笔试不是刷完题就结束每一道题背后暴露的知识点薄弱项都值得花时间补上。
返回列表