十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

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

字节跳动2017客户端实习笔试题解析:算法基础与计算机核心考点

字节跳动2017客户端实习笔试题解析:算法基础与计算机核心考点 字节跳动2017客户端工程师实习生笔试题放到今天来看依然有很强的参考价值。倒不是说题目本身多难而是它代表了大厂在招客户端实习生时最看重的那批基本功——数据结构、操作系统、网络、语言底子再加一点移动端特有的知识。我当年也刷过这波题后来也帮学弟学妹做过不少拆解这里把笔试的考察思路和典型题目整理出来希望能帮准备客户端方向的同学少走弯路。1. 笔试整体结构与考点分布1.1 2017年的市场背景与笔试定位2017年正是今日头条高速增长、抖音刚起步的阶段客户端团队急需补充新鲜血液。那时候的实习生笔试不像现在分得这么细没有专门的客户端卷而是和后台、前端共用一套基础卷只是在最后多加一些客户端相关的题目。整体定位很明确不考框架API的背诵不考“你用过什么第三方库”而是考你在没接触过业务代码的情况下能不能展现出扎实的计算机底子和解决问题的能力。这和很多同学想象的不太一样——有人以为客户端笔试会考Android四大组件、iOS生命周期这些实际上这类题占比不大反而数据结构、算法、操作系统、网络这些“通用科目”才是重头戏。原因也简单实习生进去之后要学的东西很多框架可以教但基础打不牢的同学带起来太累。1.2 考点模块和题型分布根据我接触到的信息和当时的考情回忆笔试整体可以分这么几块考点模块大致占比主要题型数据结构与算法35%选择题、编程题操作系统15%选择题、简答题计算机网络15%选择题、简答题Java/编程语言基础15%选择题、代码阅读题客户端专项知识15%选择题、简答题智力题/逻辑题5%选择题笔试时长一般在90到120分钟题量在20到30道之间选择题为主外加2到3道编程题。编程题通常是在牛客网这类在线评测系统上做要自己处理输入输出。这里提醒一下不做笔试前一定先熟悉牛客网或者赛码网的答题界面尤其是编程题的输入输出格式很多人不是不会做是卡在处理输入上浪费了大量时间。2. 数据结构与算法题笔试的硬门槛2.1 高频考点链表、树、字符串、动态规划算法这块的难度大概在LeetCode中等偏下但有一个特点题目描述往往披着“实际场景”的外衣需要你剥离出来看清它的本质。比如“给定一个数组找出和为target的两个数的下标”本质就是两数之和“实现一个支持随机取值的集合”本质是哈希表加数组。2017年字节的客户端笔试算法题高频考点我总结下来是这几类链表操作反转链表、合并有序链表、链表中环的检测二叉树层序遍历、最近公共祖先、二叉树的最大深度字符串最长无重复子串、字符串匹配、括号匹配动态规划爬楼梯、背包问题变形、最长公共子序列数组技巧滑动窗口、双指针、前缀和这些题目有一个共同特征看起来不难但边界条件非常多。比如链表反转题目本身逻辑很简单但如果你没有处理空链表和单节点链表写出来的代码一跑就崩。笔试阅卷虽然主要看测试用例通过率但边界处理好不好很多时候直接决定了你的代码能不能全过。2.2 典型算法题解析以链表反转为例这是我在当年笔试印象里非常高频的一道题。题目描述有很多变体有的直接说“反转一个单链表”有的说“给定一个链表每k个节点一组翻转”但最基础的版本反而是最常考的。迭代法写起来其实很固定public ListNode reverseList(ListNode head) { ListNode prev null; ListNode curr head; while (curr ! null) { ListNode nextTemp curr.next; curr.next prev; prev curr; curr nextTemp; } return prev; }这段代码的核心理念就一句话遍历过程中把当前节点的next指向前一个节点。但为什么很多人在笔试时候写错主要就是顺序搞乱了。你先保存下一个节点再改当前节点的next最后移动prev和curr——这个顺序必须固定。我见过有同学先改next再保存结果链表直接断掉了后面全部丢失。再来说说二叉树层序遍历这个题在2017年笔试里也出现过。题目要求按层输出二叉树节点比如根节点一行第二层一行以此类推。关键是输出格式很多题要求返回值是ListListInteger每一层是一个子列表。public ListListInteger levelOrder(TreeNode root) { ListListInteger result new ArrayList(); if (root null) return result; QueueTreeNode queue new LinkedList(); queue.offer(root); while (!queue.isEmpty()) { int size queue.size(); ListInteger level new ArrayList(); for (int i 0; i size; i) { TreeNode node queue.poll(); level.add(node.val); if (node.left ! null) queue.offer(node.left); if (node.right ! null) queue.offer(node.right); } result.add(level); } return result; }这题的考点不只是BFS更关键的是如何在队列中区分每一层的边界。你需要在每一轮循环前记录当前队列的长度这个长度就是当前层的节点数然后只处理这么多节点。很多同学的错误在于直接用了queue.size()作为循环条件但队列在循环过程中不断有新节点入队size一直在变导致分层错误。2.3 算法题的时间复杂度与边界条件算法题部分还有一个隐藏考察点你会不会分析时间复杂度和空间复杂度。有些题目你用了暴力的O(n²)解法测试用例比较小也能过但面试官看代码会皱眉。比如两数之和如果你两层循环硬找虽然能过但这不是他们期待的解。哈希表一次遍历的O(n)解才是正解。public int[] twoSum(int[] nums, int target) { MapInteger, Integer map new HashMap(); for (int i 0; i nums.length; i) { int complement target - nums[i]; if (map.containsKey(complement)) { return new int[] { map.get(complement), i }; } map.put(nums[i], i); } return new int[] {}; }再说边界条件处理我习惯在写完代码后立刻检查三件事输入为空的时候会怎样只有一个元素的时候会怎样最大数据量的时候会不会超时或溢出比如两数之和数组为空直接返回空数组链表题要考虑空节点和单节点动态规划要考虑n0或n1的情况。这些检查在平时练习时就要养成习惯考试时才能下意识做到。3. 语言基础与编程能力考察3.1 Java方向集合类、JVM、并发字节在2017年客户端笔试的Java题占了不少比重。Android开发的主语言是Java所以Java基础好不好直接反映了你后面能不能快速上手业务。Java方向考察集中的点HashMap的底层原理数组加链表的结构、hash计算、扩容机制到了2017年这个时间点还要了解红黑树化的条件链表长度超过8且数组长度超过64。为什么用红黑树因为链表太长时查找退化成了O(n)红黑树能保证O(log n)的查找效率。JVM内存分区堆、栈、方法区、程序计数器、本地方法栈。常考的是“哪些线程共享、哪些线程私有”这类区别题。并发基础synchronized和Lock的区别、volatile的可见性、线程池的参数corePoolSize、maximumPoolSize、workQueue。线程池这块尤其爱考因为客户端里有很多异步任务线程池用得好不好直接影响性能。这里我多说一句当时很多同学死记硬背“HashMap线程不安全HashTable线程安全”但笔试一旦问“为什么不安全”就答不上来了。原因其实不复杂多线程同时put时如果两个key算出来的桶位相同且两个线程同时触发扩容就可能出现数据覆盖甚至死循环JDK1.7头插法时有这个问题。你得理解原理而不是背结论。3.2 Objective-C/C方向指针、内存管理、Runtime2017年的客户端笔试还有一个特点Java和Objective-C/c会分开考。如果你投的iOS岗位会有一批OC相关的题目如果你投Android主要考Java。但不管哪个方向指针和内存管理都绕不开。iOS方向常考的ARC下对象生命周期strong、weak、copy、assign的区别。这里有个经典陷阱——为什么assign不能用于对象因为assign不会增加引用计数对象释放后再访问就是野指针崩溃所以对象要用weak或strong。Runtime消息机制objc_msgSend的流程——先查缓存再查方法列表找不到就沿继承链向上找再找不到走消息转发。笔试一般只考到“消息发送流程”这一步。Block的循环引用Block会强引用它捕获的外部变量如果Block被self持有而Block内部又引用了self就形成了循环引用内存无法释放。解决方式是用__weak修饰self。C/C方向常考的指针与引用的区别深拷贝与浅拷贝堆上的内存分配与释放3.3 代码阅读题理解执行顺序笔试题里还有一种很让人头疼的题型——给一段代码问输出结果是什么。这类题考的是你对语言细节的理解而不是简单的语法。当年有一道典型的题public class Test { public static void main(String[] args) { Integer a 127; Integer b 127; Integer c 128; Integer d 128; System.out.println(a b); System.out.println(c d); } }答案是true和false。为什么因为Integer从-128到127的取值会走缓存直接从缓存里取对象所以a和b指向同一个对象而128超出了缓存范围会创建新对象c和d指向不同对象。这题考的就是基本包装类型的缓存机制。做题时看不懂的话说明你对Java基础语法的掌握还不够细。这类题没有捷径只能靠平时多刷、多总结。我建议把常见的坑整理成一个文档标注好“易踩坑点”考试前翻一遍效果比临时抱佛脚好得多。4. 计算机基础操作系统与网络4.1 操作系统进程线程、内存管理、死锁操作系统这块当时的笔试难度不算深但考察面很广。核心考点集中在三个方面。进程与线程的区别是老生常谈但笔试会有更细的变体。比如“进程间通信方式有哪些”或者“进程和线程谁拥有独立的地址空间”。答案本身不难但要注意线程是CPU调度的基本单位进程是资源分配的基本单位进程之间地址空间独立同进程的线程共享地址空间。很多人把调度单位和资源分配单位搞反了一失分就是送分题变送命题。内存管理的考点主要是虚拟内存、分页分段、页面置换算法。这些概念对客户端开发来说好像不太直接但实际上Android的OOM问题、iOS的Jetsam机制都和内存管理有关。笔试不会考得太深但至少要知道虚拟内存是为了隔离进程地址空间、让每个进程都看到完整的地址空间页面置换算法包括FIFO、LRU、LFU至少能说出LRU的思路——最近最久未使用的页面先淘汰。死锁几乎是必考项四个必要条件互斥、持有并等待、非抢占、循环等待。这四个条件缺一不可打破任意一个就能解除死锁。答题的时候建议把四个条件写全再结合“如何避免”来答比如“按固定顺序加锁”“使用超时机制”这样得分点更全。4.2 计算机网络TCP/UDP、HTTP、握手挥手网络这块的考点稳定得让人放心——TCP三次握手、四次挥手、TCP和UDP的区别、HTTP方法、状态码。但有几个细节需要注意。三次握手为什么是三次不是两次这个问题的关键点在于两次握手无法确认客户端的接收能力。假设只有两次握手服务端发出SYNACK后如果这个包丢失了服务端不知道客户端是否收到但客户端又以为连接已建立就会发送数据可服务端此时还没准备好数据就丢了一半。三次握手时服务端必须等客户端再回一个ACK才确认双方收发都正常。四次挥手为什么比握手多一次因为TCP是全双工的每一方的关闭都需要独立确认。A发FIN表示“我这边数据发完了”B回ACK表示“知道了”但B的数据可能还没发完等B发完了再发FINA再回ACK这才是完整的关闭流程。笔试如果考状态变化记住TIME_WAIT是在主动关闭方出现的目的是让迟到的报文在网络中消失避免影响新连接。HTTP和HTTPS的区别也是高频题。HTTP的端口是80HTTPS是443HTTP是明文传输HTTPS加了TLS/SSL加密层HTTPS需要证书证书的作用是让客户端确认服务端的身份。2017年的时候Android开发已经要求默认使用HTTPS了Google Play对明文HTTP的限制也是从那时开始强化的所以笔试考这个很有实际意义。4.3 典型的死锁题目有一类题目非常典型就是“系统中有n个进程每个进程需要m个资源问至少需要多少资源才能保证不死锁”。这类题看着像数学题其实套公式就能解。公式是每个进程先分配m-1个资源这时所有进程都在等最后一个资源系统再增加1个资源就能保证至少有一个进程拿到全部资源运行完毕。所以最少资源数 n × (m-1) 1。举个例子3个进程每个进程需要5个资源最少需要多少个资源才能保证不死锁答案是 3 × (5-1) 1 13。这类题只要理解了“最坏情况下每个进程都差一个资源”这个逻辑基本就不会错了。5. 客户端专项知识Android与iOS5.1 Android方向高频考点如果你投的是Android客户端除了通用基础题还会有一批Android专项题。2017年时的Android考点主要集中在Activity启动模式standard、singleTop、singleTask、singleInstance。重点考的是它们的适用场景。singleTop应用在通知栏跳转避免重复创建同一个ActivitysingleTask应用在外部分享链接保证应用中只有一个实例singleInstance应用在来电界面这类需要独立任务的场景。笔试有时候会给一个场景让你选启动模式或者让你描述“某个模式下Activity的onNewIntent会不会被调用”。Handler机制Looper、MessageQueue、Handler三者之间的关系。要能说清楚主线程默认有一个LooperLooper通过loop()方法不断从MessageQueue里取消息Handler把消息post到MessageQueue里并在takeMessage之后回调handleMessage。很多人搞混的点是MessageQueue是链表结构而不是队列结构插入消息的时间复杂度是O(1)但首尾节点的操作有优化。new Thread(new Runnable() { Override public void run() { Looper.prepare(); Handler handler new Handler() { Override public void handleMessage(Message msg) { // 处理消息 } }; handler.sendEmptyMessage(0); Looper.loop(); } }).start();这段代码就是一个子线程创建Handler的标准流程。笔试经常问“子线程能不能直接new Handler”答案是不能必须先调用Looper.prepare()因为Handler在构造时会尝试获取当前线程的Looper如果没有就会抛RuntimeException。Binder机制是Android IPC的核心。笔试一般不会考得太深但要能说出Binder的优点——性能高一次拷贝、安全内核校验UID、可以调用远程对象。注意别和Socket混淆Socket是走内核网络协议栈的性能远不如Binder。5.2 iOS方向高频考点iOS专项题主要围绕内存管理和Runtime展开。RunLoop是iOS面试笔试的常客。RunLoop的本质是一个事件循环在NSRunLoop内部维护了一个线程和一堆事件源Source、Timer、Observer没有事件时线程睡眠有事件时被唤醒。为什么主线程的RunLoop默认开启而子线程默认不开因为主线程需要一直在RunLoop里处理UI事件而子线程如果不需要处理事件就没必要浪费资源。ARC和MRC的区别。ARC是编译器在编译期自动插入retain/release/autorelease代码不是运行时GC。笔试题有时候给一段代码问对象的内存引用计数是多少或者问对象什么时候被释放。这类题需要你对autoreleasepool的执行时机非常敏感——autorelease的对象不是立刻释放而是等当前autoreleasepool被drain时才释放。消息发送机制[obj performSelector:selector(foo)]实际上会被编译器翻译为objc_msgSend(obj, selector(foo))。objc_msgSend的查找流程是先查obj的isa指针找到类对象再查类的缓存方法列表没有就查方法列表再没有就沿继承链往父类找还没有就尝试消息转发。理解了这条链路很多诡异的问题就迎刃而解了。5.3 客户端相关的UI布局与性能题两个方向都会考一点UI和性能。Android考布局优化——为什么RelativeLayout能减少嵌套层级、ConstraintLayout什么时候引入的iOS考Autolayout和frame的区别。性能题一般问“列表卡顿怎么排查”或者“App启动慢可能是什么原因”。列表卡顿在2017年是一个特别热门的话题因为那时各家都在做信息流产品列表就是门面。核心考点包括ViewHolder复用的目的减少findViewById和inflate的开销、图片加载为什么要做尺寸压缩和内存缓存大图直接加载会造成OOM、onBindViewHolder里不要做耗时操作否则滑动会卡顿。如果你能在答题时说出“用RecyclerView取代ListView”“用Glide或Fresco做图片加载”“在onCreateViewHolder里只做inflate数据绑定放onBindViewHolder”这些细节分数会明显高一些。6. 备考策略与踩坑经验6.1 三轮复习法基础、刷题、模拟结合我自己的经验给准备笔试的同学一个三轮复习建议。第一轮是基础巩固时间1到2周。把数据结构、操作系统、网络这三大科的基础概念过一遍重点看各种树的操作、排序算法的时间复杂度对比、TCP和UDP的区别、进程与线程的区别。这一轮不需要做难题但要把概念吃透。第二轮是刷题强化时间2到3周。在LeetCode上按标签刷题重点刷数组、链表、字符串、二叉树、动态规划这五类。刷题量不用太大每天3到4道新题加复习旧题就够了关键是每道题都要做到能独立写出来并跑通。不要只看题解看题解五分钟不如自己想题半小时。第三轮是模拟实战考前1周。在牛客网上找几套真实的笔试模拟题严格按照考试时间来做。需要注意的有两点一是熟悉在线编辑器尤其是输入输出处理——很多笔试平台要求自己写while (scanner.hasNextLine())这种循环来读取多行输入二是练时间分配遇到卡住的题先跳过别在一道题上耗太久。6.2 常见问题与避坑速查常见坑具体表现解决方法输入输出不熟悉明明答案对了就是读不进数据提前在牛客网练3道输入输出题边界条件遗漏空链表、空数组直接崩写代码前先想三个边界用例时间分配失衡一道编程题卡40分钟先做选择题编程题留足70分钟只背概念不理解换个说法就答不出来每个知识点自己用自己的话讲一遍忽视复杂度要求暴力解法超时刷题时顺手做复杂度分析6.3 时间规划与心态调整最后说说时间规划。如果是大二下或者大三上开始准备时间是足够充裕的。建议的节奏是先花两周过基础再花三周刷题考前一周集中做模拟。如果是临时抱佛脚至少也要保证算法题每天3道不能断选择题的知识点用思维导图串一遍客户端专项知识重点看启动模式和内存管理。心态上有一点特别重要不要因为一道题没做出来就崩。笔试不是要你考满分而是要在限定时间内尽量多拿分。遇到不会的题先标记跳过把会做的做完再回头啃。我当年有一个同学算法题只做出来一道半但因为选择题几乎全对照样进了面试。这说明笔试是一个整体评估单点失误影响没有你想象得那么大。说实话2017年这批笔试题放到今天难度上并没有降级反而因为客户端开发越来越成熟公司对基础的要求更细了。但基础题的本质从来没变过——数据结构、操作系统、网络、语言功底再加一点移动端常识。把这五块吃透不管去哪家面试心里都有底。
返回列表