ARTICLE DETAIL

资讯详情

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

字节跳动客户端实习笔试全解析:考点、编程题与代码习惯

字节跳动客户端实习笔试全解析:考点、编程题与代码习惯 字节跳动2017客户端工程师实习生笔试题我当年是真刀真枪考过的。那会儿今日头条已经火到不行身边投客户端实习岗位的同学一大片笔试链接发过来的时候我还挺紧张。整场考试90分钟Web编辑器没有IDE提示没有自动补全选择题和编程题混在一起时间卡得让人喘不过气。现在回头看这场笔试考察的东西其实非常有指向性基础扎实不扎实、代码习惯好不好、现场抗不抗压基本一测就知道。这篇文章就把我当时遇到的题型、踩过的坑、后来复盘总结的考点系统性整理出来适合正在准备大厂客户端方向实习笔试的同学参考。1. 2017年字节跳动客户端实习生笔试全景1.1 招人逻辑什么样的实习生能过笔试2017年字节跳动的客户端团队扩张得非常猛。今日头条App当时已经有几亿用户内容资讯、视频、社交功能在频繁迭代客户端是离用户最近的一层。团队招实习生不是招来“学习”的而是希望尽快上手写业务代码。所以笔试不会出特别偏的题但会把基本功测得很透因为移动端不像后端那样有海量并发请求但对内存占用、页面渲染、网络请求、数据持久化都必须特别敏感这些能力必须靠基础功底撑起来。从笔试设计角度看它要筛选的是“能写代码的人”而不是“背过八股文的人”。所以选择题大部分是概念辨析编程题则考察你能否在没有任何提示的环境下把思路转成语法正确的代码。很多同学到了这种环境就露馅了平时在IDE里靠快捷键和自动补全写代码一旦换成简陋的在线编辑器连StringBuilder都可能拼错。这一点恰恰是出题人想看到的他们要的是离开工具之后依然稳的人。还有个细节2017年移动端招人明显比后端更看重操作系统和网络基础。原因很简单客户端工程师每天都要和线程调度、内存管理、网络请求打交道如果连进程线程、TCP这些概念都含糊后面做性能优化时基本寸步难行。所以笔试里操作系统和网络的分值占比一点都不低很多人栽在低估这部分上。1.2 题型结构与整体考察目标当年的在线笔试平台一般是牛客网或者赛码网考试界面很简单一个代码编辑器、一个倒计时、一排题目列表。整体结构大致是这样题目类型大致题量核心考察点建议用时不定项选择题20道左右Java/C、数据结构、操作系统、Linux、网络25分钟编程题2-3道编码能力、算法思维、边界处理55分钟简答题0-1道客户端机制理解如Handler10分钟选择题里最烦人的是不定项选择多选、漏选都不得分有时候一道题四个选项里三个半都对就是有一个模棱两可的干扰项专门坑那种“好像在哪见过但记不清”的人。编程题通常第一题偏简单后面开始上强度考的核心不是你会不会某道LeetCode原题而是你能不能把常见算法在限定时间内写对、写完整。这里多说一句笔试不是看你刷了多少题而是看你在紧张环境下能否稳定输出。我当时有个朋友刷了300多道LeetCode结果笔试第一道链表题就卡在边界条件上最后只AC了一道。反而是另一个刷题量不到他一半的同学三道题全都跑通。差别就在代码习惯尤其是边界条件的处理这一点后面会重点展开。2. 核心考点逐个拆解从选择题到编程题2.1 Java/C基础数组和指针是重灾区每年Java/C选择题里“数组和指针”永远是重灾区字节2017年的笔试也不例外。当年考过的几类典型题型我到现在还记得很清楚第一类是sizeof问题。比如int a[5]; int *p a;sizeof(a)是20字节假设int占4字节而sizeof(p)是8字节64位系统下指针大小。很多人想当然觉得“数组名不就是指针嘛”一写答案就错。实际上数组名在大多数表达式中会退化为指针但在sizeof里不会这是一个极其经典的考点。第二类是指针加减。比如int a[5] {1,2,3,4,5}; int *p a;问*(p3)是多少。答案是4也就是a[3]。这里容易错的是把p3理解成“地址加3个字节”实际上指针加法是按指向类型的大小来移动的int型指针加1移动4个字节。第三类是二维数组的指针。int (*p)[4]是“指向含4个int元素的一维数组的指针”而int *p[4]是“一个含4个int指针的数组”。这个区别在选择题里出现频率极高只看声明就能劝退一批人。Java部分的常考内容相对固定HashMap的底层结构数组链表/红黑树以及put流程、和equals的区别、String/StringBuilder/StringBuffer的区别、final/finally/finalize的区别、try-catch-finally中return的执行顺序。其中finally那题我印象很深因为看起来简单实际很容易掉坑try { return 1; } finally { System.out.println(finally); }这段代码会先执行finally块中的输出然后才执行return 1。但如果是这样写try { return 1; } finally { return 2; }那最终返回的就是2因为 finally 的 return 会覆盖 try 中的 return。这种题目放在选择题里考察的就是你对 JVM 字节码执行顺序的理解死记硬背很容易在变体题上翻车。注意在线笔试没有IDE提示写完代码只能靠肉眼检查。数组和指针相关的代码务必在纸上把内存布局画出来能大大降低出错率。2.2 数据结构与算法编程题的套路与变体2017年字节客户端笔试的编程题基本可以归纳到几类字符串处理、链表操作、二叉树遍历、经典动态规划。这些题目在LeetCode上都有原型但笔试里往往加了一些细节变体防止你直接背答案。字符串处理是常客最长不重复子串、字符串压缩、括号匹配、翻转单词顺序都是高频题。考字符串的核心原因是它天然能考察你对索引、边界、字符编码这些细节的敏感度。比如“翻转字符串中的单词顺序”很多人的第一版代码会在开头和末尾有多余空格时出错。链表操作同样高频反转链表、判断链表是否有环、两两交换相邻节点、合并两个有序链表都是需要默写的题目。链表题得分的关键不是思路而是画图。只要把指针的变化画出来代码就不会写错。二叉树题目中层序遍历、最大深度、最近公共祖先是最常出现的。层序遍历需要借助队列这个倒不难但要注意题目要求的是按层输出二维数组还是一维数组这决定代码的写法。最近公共祖先那道题则要理解递归的返回值语义很多人在这里卡住。动态规划在客户端笔试里不会考特别难的题爬楼梯、打家劫舍、最长上升子序列这类经典题就够了。但要注意笔试环境里动规题往往有几个隐藏陷阱数组长度可能为0、数字可能为负数、结果可能溢出int范围。很多LeetCode跑得通的代码到了笔试判题系统里就因为这些问题超时或报错。这里我得说个经验编程题不光看对不对还看复杂度。比如“求滑动窗口最大值”这道题你用暴力解法时间复杂度是O(n*k)数据一大就超时。考场上如果只追求“能跑出结果”很可能在最后一个测试用例上卡死。所以写代码之前先估算一下数据范围再决定用什么算法。2.3 操作系统、Linux与网络客户端不能丢的地基这套题里操作系统和网络的分值占比出乎意料地高。进程和线程的区别、死锁的四个必要条件、虚拟内存的作用、页面置换算法这些都是选择题和简答题的常客。尤其是死锁四条件——互斥、持有并等待、不可剥夺、循环等待几乎每次笔试都会出现只是换着花样考。TCP/IP部分三次握手、四次挥手、为什么TIME_WAIT要等2MSL是经典的“老八股”问题。客户端工程师更需要理解这些因为App的网络请求库底层全是TCP和HTTP。我记得有一道题问“HTTP和HTTPS的区别”选项里有一个是“HTTPS使用对称加密传输数据”这个说法其实不全对因为HTTPS握手阶段用的是非对称加密协商密钥之后的数据传输才用对称加密。这种选项就是专门用来淘汰“一知半解”的人的。Linux基础也会考几道比如查看进程用什么命令ps、查看端口占用用什么命令netstat、查看磁盘使用情况用什么命令df。如果你是客户端方向还可能遇到“如何查看Android进程的CPU占用”这类结合题。客户端可能不太常用Linux但这些命令搞懂了后面做性能分析和日志排查时会非常省力。提示选择题遇到不确定的选项先把它和你确定的知识点做对比排除明显矛盾的选项剩下的再猜。不定项选择宁少勿多选错了整题零分漏选至少还有机会拿一部分分有的平台漏选给一半分。3. 典型编程题实操三题拿到80%分数的写法3.1 LRU缓存用LinkedHashMap还是手写双向链表LRU缓存这道题在字节笔试里出现过多次变体2017年前后尤其频繁。题目要求实现一个LRU缓存支持get(key)和put(key, value)get和put的时间复杂度都是O(1)。如果大家已经熟悉Java最快能AC的写法是利用LinkedHashMapimport java.util.LinkedHashMap; import java.util.Map; public class LRUCacheK, V extends LinkedHashMapK, V { private final int capacity; public LRUCache(int capacity) { super(capacity, 0.75f, true); this.capacity capacity; } Override protected boolean removeEldestEntry(Map.EntryK, V eldest) { return size() capacity; } public static void main(String[] args) { LRUCacheInteger, String cache new LRUCache(2); cache.put(1, a); cache.put(2, b); cache.get(1); // 访问key1让它变成最近使用 cache.put(3, c); // 容量超了淘汰最久未使用的key2 // 输出 {1a, 3c} System.out.println(cache); } }这里最关键的是LinkedHashMap构造器的第三个参数accessOrder设为true后map会按照访问顺序排序每次get或者put被访问的entry都会移到链表尾部。然后重写removeEldestEntry当当前容量超过设定容量时就移除链表头部的entry也就是最久未使用的那个。核心就这两行。但要注意这么写虽然AC很快却不像一个“客户端工程师”该有的深度。我记得当时面试官后续追问了一句你了解LinkedHashMap底层怎么维护访问顺序吗如果之前没研究过很容易卡壳。所以我的建议是笔试拿分用LinkedHashMap但笔试结束后一定要自己手写一遍双向链表HashMap的实现。手写版本核心思路是——HashMap负责O(1)查找双向链表负责O(1)插入和删除每次访问节点就把它移到链表尾部。3.2 反转链表迭代与递归两种解法都要会反转链表是客户端笔试里最经典的链表题没有之一。题目非常直接给定一个单链表头节点返回反转后的链表。迭代写法在笔试中最稳空间复杂度O(1)class ListNode { int val; ListNode next; ListNode(int val) { this.val val; } } public ListNode reverseList(ListNode head) { ListNode prev null; ListNode cur head; while (cur ! null) { ListNode nextTemp cur.next; cur.next prev; prev cur; cur nextTemp; } return prev; }这段代码的要点是提前保存cur.next否则一旦把cur.next指向prev原来的后半段链表就丢了。我当年笔试时就没写这行结果链表直接从中间断掉测试用例全挂。后来我养成一个习惯凡是涉及指针修改的链表题第一件事就是问自己“改之前要不要先存一下原始next”。递归写法也建议掌握它能展示你对递归的理解。思路是先反转head之后的所有节点然后把head接到反转后链表的尾部public ListNode reverseList(ListNode head) { if (head null || head.next null) { return head; } ListNode newHead reverseList(head.next); head.next.next head; head.next null; return newHead; }递归版本的空间复杂度是O(n)因为递归调用栈会占用空间。笔试里如果链表很长有可能爆栈所以迭代法是默认答案。如果题目没有额外要求优先写迭代。注意在线的判题系统通常会有“特殊输入”测试比如空链表、单节点链表、两个节点的链表。写代码之前先在脑子里跑一遍这几个边界情况能救回很多测试点。3.3 字符串压缩边缘情况和StringBuilder的取舍字符串压缩也是一道很典型的“会者不难”的题。题目类似给定一个字符串把连续相同的字符压缩成“字符出现次数”例如aabcccccaaa变成a2b1c5a3。如果压缩后字符串长度不小于原字符串则返回原字符串。我当时写的版本是public String compress(String s) { if (s null || s.length() 0) { return s; } StringBuilder sb new StringBuilder(); char cur s.charAt(0); int count 1; for (int i 1; i s.length(); i) { if (s.charAt(i) cur) { count; } else { sb.append(cur).append(count); cur s.charAt(i); count 1; } } sb.append(cur).append(count); String compressed sb.toString(); return compressed.length() s.length() ? compressed : s; }这题有两个关键点。第一必须用StringBuilder而不是在循环里直接用String拼接。因为Java的String是不可变对象每次都会产生新对象数据大了之后时间会爆炸面试里也常考这个点。第二最后别忘了处理最后一个字符的计数很多人循环结束就以为完事了漏掉了最后的sb.append(cur).append(count)。扩展延伸一下如果题目要求不区分连续而是统计整个字符串中每个字符出现的次数那就要换HashMap来统计输出顺序不一定有序需要根据题目要求决定是否排序。还有变体是要求按字符首次出现的顺序输出那就需要LinkedHashMap。这些变体都是同一类考点遍历字符串、统计、按需输出。4. 笔试实战复盘失分点、时间分配与后续准备4.1 答题顺序与时间分配笔试的90分钟怎么分配直接决定你的最终成绩。我的建议是不要按题目顺序做。收到试卷后先花2分钟把所有题目扫一遍尤其是编程题快速判断哪些是自己熟练的、哪些是看着就头疼的。然后按“易得分 → 必须得分 → 能拿几分算几分”的顺序做。具体来说先把最有把握的一道编程题AC掉这样心里有底后面就算时间紧也不会慌。然后做选择题因为选择题是不定项选考察的往往是记忆类知识趁脑子清醒时正确率更高。简答题如果有一道Handler机制之类的可以放在选择题之后写因为它不需要太多思考主要是把思路写完整。最后再啃剩下的编程题如果卡了10分钟以上果断跳过或者先写暴力解法保底。很多同学喜欢先做选择题觉得编程题思维负担大。但我的经验是选择题里那些不定项很容易让人反复纠结时间不知不觉就过去了。编程题如果放到最后半小时心理压力会非常大手一抖代码就容易写崩。先搞定编程题心态会稳很多。4.2 那些年我踩过的失分点我在那次笔试里踩过的坑以及在后来帮同学复盘时看到的常见失分点集中整理一下第一不处理空输入。编程题给你一个head你直接while (cur.next ! null)如果head本身是空的直接空指针异常。正确做法是开头先写if (head null)的判断。这个习惯在IDE里不明显因为IDE会帮你补全但在线编辑器里全靠手写很容易漏。第二数组越界。尤其是涉及动态规划的题dp[0]、dp[1]的初始化没想清楚循环里一跑就数组越界。还有字符串的charAt(i)在循环最后一位时如果用了i1也会越界。第三忘了处理Java基本细节。比如方法返回类型是String但最后返回的是StringBuilder变量名拼写不一致类名和文件名对不上。这些都算编译错误在线编辑器可不会帮你自动修复。第四选择题被“不确定”拖死。不定项选择多选漏选都扣分很多人因为一个选项不确定就反复改最后把正确答案改没了。考场上我吃过的亏就是“再看一遍觉得A不对”结果A是对的。后来我学乖了除非能从原理上否定一个选项否则不要轻易改第一感觉。第五时间分配失衡。编程题第一题简单有些人写得非常卖力追求漂亮解法结果第二、第三题没时间看。笔试的目标不是完美而是总分最大化简单题能AC就不要贪。4.3 笔试通过后面试前应该准备什么如果你顺利收到了面试通知恭喜第一关过了。但别高兴太早笔试只是筛基本功面试才是真正看潜力的时候。我当时在准备面试时主要做了四件事一是复盘笔试题。笔试结束后趁记忆还热把每道题重新做一遍尤其是没做出来的题查漏补缺。很多面试官会拿你笔试试卷当素材直接问你“当时这道题为什么这么写”“现在还有没有更优解”。如果答不上来会非常减分。二是把项目经历整理成STAR结构。面试官几乎必问“你做过什么项目”。不要只说“我做了个App”要说出背景、任务、行动、结果最好还有具体数据比如“优化了列表加载卡顿率下降30%”。三是客户端核心知识点系统过一遍。Android方向重点准备四大组件、Handler机制、RecyclerView的缓存复用、内存泄漏常见场景、OkHttp的请求流程。iOS方向重点准备OC内存管理、RunLoop、Block、KVO/KVC这些。这些都是客户端实习生面试的高频区。四是要能白板写代码。面试里会让你现场写题而且这次没有在线判题系统面试官看着你写。平时练习时一定要养成“不开IDE也能写对”的习惯多拿笔在纸上画或者在文本编辑器里写代码不运行写完再人工检查。这个能力练好了面试的容错率会高很多。我个人在实际操作中的体会是字节这种笔试筛的不是“天才”而是“稳定的人”。你不需要每道题都完美但你要让阅卷的人感觉到你的思路是清晰的代码是可信的。我当时笔试编程题只完整AC了两道选择题还改错了好几道但依然收到了面试通知。后来跟当时的面试官聊起来他说笔试主要看两件事一是遇到问题会不会拆解二是写出来的代码能不能维护。这两个标准后来也一直影响着我带人的方式。如果你现在正在准备客户端方向的实习笔试最后再分享一个小技巧不要只刷题不看基础也不要只背八股不写代码。把操作系统、网络这些“地基”打牢把常见算法题练到能默写再把代码习惯磨到不依赖IDE也能稳这三件事做到笔试通过率会高很多。
返回列表