ARTICLE DETAIL

资讯详情

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

字节跳动2017后端实习笔试复盘:Java基础、网络与算法全解析

字节跳动2017后端实习笔试复盘:Java基础、网络与算法全解析 2017年字节跳动还不像现在这样人尽皆知但挂在牛客网上的后端工程师实习生笔试题已经在学生圈里传得很广。那会儿我投的是后端实习岗打开笔试页面第一反应是“题量比想象中大”120分钟里既有30道左右的选择题也有后面分开算分、限时的编程题。选择题覆盖Java、Linux、操作系统、计算机网络和数据库编程题则集中在链表、数组、字符串这几类基础算法上。整套题做下来最大的感受是它不考偏题怪题但每一道都在试探你有没有真正写过代码、有没有被线上问题毒打过。这篇文章就是我对这套题的一份完整复盘把考点拆开、把解法写透也给准备后端实习笔试的同学一条比较清晰的复习路线。1. 先拆解这套笔试题的整体思路1.1 题型与板块构成从整体结构看这套笔试分为选择题和编程题两大部分。选择题又分成单选和多选加起来大概30道内容横跨Java语法与集合源码、JVM内存模型、进程线程、TCP/IP、Linux命令、关系型数据库索引和SQL。编程题一般是三道偶尔四道每一道都不算特别大的算法题但边界条件非常容易踩坑。我当时做题的顺序是先做选择题再写编程题后来复盘发现这个顺序不一定是最优的。选择题里多选题占了不少比例而多选规则是“少选不得分、多选也不得分”稍不注意就会在某个选项上犹豫三四分钟。如果把这些时间省下来留给编程题总分通常会更高。所以后来我给别人内推时都会提一句拿到卷子先花两分钟扫一遍所有题目心里有个轻重缓急再决定先做哪块。1.2 字节在笔试里到底想筛什么样的人字节当时正处于团队快速扩张期后端岗位对实习生的要求并不是“会写业务CRUD”而是“计算机基础够扎实遇到问题能自己拆解”。笔试筛人的逻辑也围绕这点展开选择题把基础不牢的人过滤掉编程题看候选人能不能把思路快速转成可运行代码。我后来跟一位参与出题的工程师聊过一次他说笔试判分看几个维度正确性排在第一位其次是边界处理再次是时间复杂度和代码风格。很多人编程题主逻辑写出来了但一测空数组、单元素数组、溢出情况就挂。这正是笔试和平时刷题最大的区别平时在IDE里跑样例很舒服笔试系统只会告诉你“通过了多少用例”不告诉你具体挂在哪。习惯在写代码之前先枚举边界条件的人在这类环境里优势非常明显。建议准备阶段不要只刷难题把LeetCode简单题和中档题吃透笔试通过率会比想象中高很多。1.3 为什么这套题到今天还有参考价值后端工程师笔试的考察框架这几年其实没有本质变化。Java集合类、并发编程、网络协议、操作系统、Linux命令、基础算法依然是几乎所有大厂后端岗的必考范围。字节2017年的这套题代表了一个典型的“重基础、重原理”出题思路和最近几年各厂实习笔试放在一起对比你会发现考点重合度可能超过70%。当然题目难度和范围也在水涨船高。2017年的编程题基本停留在链表反转、二分查找、字符串处理的层次现在很多公司还会加背包问题、树形DP、单调栈这类进阶内容。所以这套题更适合作为复习起点而不是全部。2. Java基础与集合类高频题不只是背答案2.1 数组与指针写Java也要懂的内存模型热词里“数组和指针笔试题”出现了很多次这套卷子里也确有这个方向只是换成了Java的问法。题目大概是给定int[] a {1,2,3}; int[] b a; b[0] 99;问a[0]的值是多少。答案是99不是1因为数组变量保存的是对象的引用b a只是把引用复制了一份两个变量指向同一块堆内存。这道题对写过C/C的同学来说很容易理解但只写过Java、没深究过引用语义的人反而容易错。要真正理解它需要建立Java内存模型的整体概念栈上的局部变量保存基本类型值或对象引用堆上存放真正的对象数据方法区存放类信息、常量和静态变量。数组在Java里也是对象new出来的数组一定在堆里局部变量只是指向它的引用。C/C方向的题还会再往前走一步比如问int *p NULL; p;之后p指向哪里。答案是地址增加sizeof(int)个字节。指针运算的单位是类型大小不是字节。这个考点在选择题里出现频率不低底层基础扎实的人基本秒选不扎实的就会在0x0、0x4这类选项里乱猜。2.2 String、ArrayList、HashMap的连环问集合类是Java选择题的重头戏2017年这套卷子里出现的几个高频题目我今天还记得很清楚。第一道是String s1 abc; String s2 new String(abc);问s1 s2的结果。答案是false因为一个在字符串常量池一个在堆上引用完全不同。但题目如果换成s1.equals(s2)结果就是true因为String重写了equals比较的是内容。这个考点看似基础实际上每年都有人因为没注意“比较的是引用还是内容”而失分。第二道是ArrayList和LinkedList的区别。题目问的往往不是“哪个是数组实现、哪个是链表实现”这种一翻书就知道的答案而是换个角度考复杂度在中间插入节点谁快、在尾部追加谁快、按索引随机访问谁快。很多人只背结论“LinkedList插入快、ArrayList访问快”但真正做题时才发现LinkedList在已知节点的情况下插入是O(1)按索引插入前需要先遍历到那个位置所以总体是O(n)。而ArrayList在尾部的追加均摊复杂度是O(1)头部插入因为要移动元素则是O(n)。第三道是HashMap考点集合也是我印象最深的一道。题目问“JDK 1.8中链表转红黑树的阈值是多少为什么不是0”。答案是链表长度超过8并且数组长度不小于64时才树化扩容之后如果链表长度降到6以下会从红黑树退化为链表。这样设计是为了避免频繁扩容和树化之间来回切换时间和空间上取一个平衡点。2.3 集合框架复杂度题几乎每年必考字节这套题特别爱把集合和时间复杂度叠加在一起考。比如问“在ArrayList头部不断插入n个元素总时间复杂度是多少”。如果只答“插入是O(n)”就忽略了“不断插入”这个条件因为头部插入会触发元素整体后移做n次就是O(n²)。同样的场景换成LinkedList头部插入每次是O(1)n次是O(n)。这个考法提醒我一个复习方法把常用集合所有主要操作的时间复杂度整理成一张表对着源码确认复杂度来源而不是只背网上的八股总结。ArrayList的扩容、LinkedList的节点查找、HashMap的哈希冲突、TreeMap的红黑树平衡每条都能在源码里找到答案。整理完一次之后不仅笔试选择题能应对后续面试被追问底层原理也不会慌。3. 操作系统与Linux笔试题容易白丢分的一个板块3.1 进程线程、僵尸与孤儿操作系统部分大概有五道选择题其中最高频的是进程与线程区别、进程间通信方式、僵尸进程与孤儿进程。进程和线程那题问的是“以下哪些是线程私有的资源”选项包括栈、堆、全局变量、程序计数器。正确答案是栈和程序计数器。线程共享进程的堆、全局变量和代码段但每个线程有自己的栈和程序计数器因为CPU调度切换时需要保存各自的执行现场。我习惯用一个类比来记进程像一家公司线程是公司里的员工工位和电脑是私有的茶水间和会议室是共享的。僵尸进程和孤儿进程则是纯概念题。孤儿进程是父进程先退出子进程被init进程收养不会变成没人管的进程僵尸进程是子进程先退出父进程还没来得及调用wait进程描述符和退出状态还留在内核里。题目经常问“如何处理僵尸进程”答案是让父进程wait或者把父进程杀掉让init进程接管并回收。3.2 进程间通信方式对比这道选择题几乎必考进程间通信在每个厂的笔试题里都是常客。2017年字节的卷子里问的是“以下哪些方式可以用于不同主机上的进程通信”选项有管道、共享内存、Socket、信号。正确答案是Socket。管道和共享内存都是同一台机器上的IPC机制只有Socket能跨主机通信。这道题错的人特别多因为很多人把“进程间通信”和“网络通信”分割开理解没想到Socket同时承担了这两个角色。再细一点管道的题还会问“管道通信是单向还是双向”。普通无名管道是半双工的只能一个方向写、一个方向读而且只适用于有亲缘关系的进程。命名管道可以用于无亲缘关系的进程之间但仍然是单向的。要全双工通信要么建两个管道要么直接用Socket。这几个细节拆开来看都不难但合并到一张卷子里就成了区分“背过概念”和“理解概念”的分水岭。3.3 Linux命令题越“熟悉”的命令越容易踩坑Linux命令题通常以多选形式出现。我印象比较深的有这么几道查看系统负载的命令选了top和uptime但有人混入free。free是看内存的不是看负载的。查看某个端口监听状态选项有ss、lsof、netstat、ifconfig。ifconfig是配置网络接口的明显不对但慌乱中手滑选了它的人不少。计算文件行数正确命令是wc -l不是cat -l因为cat根本没有-l参数。在文件里查找匹配行并显示行号正确用法是grep -n pattern file很多人只记了grep忘了-n。Linux命令这块没有太多技巧最有效的复习方式就是打开终端实际操作一遍。只看命令列表真的记不牢因为指令之间的相似度太高。一个下午把高频50条命令敲一遍远比在文档里背三遍有效。4. 计算机网络与并发编程后端岗的硬骨头4.1 TCP三次握手和TIME_WAIT光背流程图远远不够网络题里TCP是永远的核心。2017年这套卷子有一道多选问“TCP连接释放过程中主动关闭方会经历哪些状态”选项有FIN_WAIT_1、FIN_WAIT_2、CLOSE_WAIT、TIME_WAIT。很多人漏选了TIME_WAIT因为习惯背“四次挥手FIN、ACK、FIN、ACK”却忽略了主动关闭方最后还要进入TIME_WAIT状态。为什么要等两个MSL时间因为要确保最后一个ACK能到达对端。一旦ACK丢失对端会重发FIN此时主动关闭方仍在TIME_WAIT中可以及时响应如果直接进入CLOSED对端会收到一个错误响应连接无法正确处理。这跟寄快递后保留一段时间的回执是同一个道理。复习这个知识点我建议把TCP状态迁移图画一遍。不需要好看但要把客户端和服务端每个状态对应关系写出来。画完之后选择题怎么变形都能应付。4.2 HTTP与HTTPS从状态码到握手过程HTTP状态码也是高频考点。题目会问“301和302的区别”“404和403的区别”。301是永久重定向302是临时重定向403是服务器拒绝请求404是资源不存在。还有一个比较容易忽略的是201表示请求成功并且服务端创建了新资源。这些状态码如果平时没有系统整理确实只能靠蒙。HTTPS那几年已经在各厂笔试题里频繁出现。它会让你简述HTTPS建立连接的过程客户端发ClientHello服务端返回数字证书和公钥客户端验证证书并生成对称密钥用服务端公钥加密后发给服务端之后双方用对称密钥加密通信。还会追问“为什么不用纯非对称加密”因为非对称加密性能太差所以用非对称加密协商密钥、对称加密传输数据二者结合。4.3 线程池和锁并发题的高频基本面并发编程主要考线程池参数和锁。线程池必考的参数是核心线程数、最大线程数、任务队列、拒绝策略。题目会给一组配置问“当任务数突然暴增时线程池的执行顺序是什么”。正确顺序是先跑满核心线程数然后任务进入队列队列满了才创建新线程直到最大线程数队列和线程都满了才触发拒绝策略。很多人把“先创建线程”和“先入队列”的顺序搞反直接丢分。volatile和synchronized的区别是另一道高频题。volatile只能保证可见性和有序性不能保证原子性synchronized能保证原子性、可见性和有序性。ReentrantLock比synchronized多了可中断、可超时、可公平锁等能力但需要手动加锁和解锁。这种题靠背很难真正掌握我建议自己写几个小demo跑一遍亲眼看到不加volatile时其他线程确实读不到最新值记忆会深刻很多。死锁那题也常见问四个必要条件互斥、占有并等待、不可剥夺、循环等待。问如何避免最简单的是破坏循环等待比如所有线程都按固定顺序加锁。5. 算法题实战复盘题目不算难但细节埋雷5.1 链表反转基础中的基础却最容易写错编程题第一道大概率是链表相关。2017年那次我遇到的版本是“给定一个链表每K个节点反转一次不足K个保持原样”。这类题第一步是“非递归原地反转单链表”核心思想是三指针pre、cur、next每次把cur.next指向pre然后三个指针整体后移。递归写法也能过但面试官后续可能会追问空间复杂度所以迭代版本最好能直接写出来。每K个反转的难点在组与组之间的衔接。实现思路是外层循环遍历每组节点内层用三指针反转反转结束后把上一组的尾节点接到当前组的新头节点。容易出错的地方是最后一组不足K个时不能反转以及组与组边界上的引用很容易断掉。写代码前先把最少三个节点的链表图画出来标清楚指针移动顺序再动键盘出错率会低很多。5.2 二分查找边界条件才是真正的主角另一道高频编程题是二分查找通常带点变形比如“查找第一个等于目标值的下标”或“查找最后一个小于目标值的下标”。很多人写二分时死循环问题基本出在mid的取值和left/right的更新上。我推荐一套固定写法while (left right)mid left (right - left) / 2查找左边界时right mid查找右边界时left mid 1。这样可以有效避免left和right相等时死循环。另一个坑是mid (left right) / 2在leftright溢出时会算错用减法形式可以直接避免。笔试环境通常没有IDE提示所以平时就固定用一套模板写形成肌肉记忆。考场上不需要临时推导能省下很多时间。5.3 最大连续子序和一道隐藏的动态规划有一年编程题里出现了“给定整数数组求连续子数组的最大和”也就是经典Kadane算法。思路很简单维护一个cur表示以当前元素结尾的最大和每轮cur max(cur nums[i], nums[i])全局ans max(ans, cur)一次遍历完成时间复杂度O(n)空间复杂度O(1)。很多人第一反应是暴力枚举两个循环直接O(n²)小数据能过大数据一定超时。笔试的判题系统是按用例规模分批跑的所以一定不要抱有侥幸心理。这道题真正的考察点就是能不能想到“当前元素要么接在前面的子数组后面要么自己重新开头”这个状态转移。5.4 字符串处理回文和去重的边界字符串题也是编程题的常客。我记得有一道“给定字符串求最长回文子串的长度”中心扩展法就够用不需要直接写Manacher。思路是枚举每个位置分别以该位置和该位置与下一个位置之间为中心向两侧扩展记录最长长度。注意两种情况都要考虑奇数长度回文以单个字符为中心偶数长度回文以两个字符中间为中心。漏掉偶数中心是这道题最典型的错误。还有一道“字符串去重”的变体要求保留第一次出现的字符且顺序不变。用LinkedHashSet或者布尔数组记录字符是否出现过顺序遍历即可。但要注意字符集问题如果题目没说明是纯ASCII默认用哈希表更稳妥否则数组容量开小了会越界。5.5 全排列与回溯多写两行代码多拿几道分字节这套卷子的编程题有时候也会放一道回溯。比如“给定一个不含重复数字的数组返回所有可能的全排列”。回溯模板不算难维护一个used数组记录哪些元素已经使用path记录当前排列递归到长度等于数组长度时加入结果然后撤销选择。我见过很多人栽在“撤销选择”这一步。回溯的本质是“递归前做选择递归后撤销”如果漏了撤销下一次循坏会带着上一次的状态继续跑整个结果就乱了。第一次写这类题时可以在纸上画一棵递归树标清楚每一层做了哪个选择、撤销了哪个选择画一遍基本就理解了。6. 答题策略和我的避坑记录6.1 时间分配不要死磕一道编程题120分钟看着不少实际上选择题如果花太久编程题很容易写不完。我当时采用的策略是“先扫描全部题目再按分值和时间排序”。选择题尽量控制在45到55分钟内一眼能看出答案的秒过想不出来的先标记最后再回头算。编程题采用“先易后难”原则。第一道链表题如果10分钟内没写出来就先跳过去写后面更简单的字符串题。笔试系统通常是按通过的测试用例数量给分哪怕只跑通一部分也有分。一道题完全没写和写出50%的用例差距比想象中大得多。6.2 多选题的蒙题与排除技巧多选是主要丢分项因为少选、多选都不得分。我的应对办法是“选项之间有强关联时优先选择那些在原理上成对出现的选项”。比如同步和互斥、可见性和有序性、半双工和全双工这类成对概念在考题里通常是捆绑出现的只选其中一个大概率是错的。另一个技巧是“判断题干里有没有否定词”。题面经常把“不是”“错误”“不包括”加粗或大写但考场上一紧张就会忽略。我自己就在一道“以下哪项不是进程间通信方式”里选了Socket直接反向送分。后来我养成了一个习惯把否定词在草稿纸上圈出来或者把题干改写成肯定句再作答。6.3 笔试复盘真正能带走什么笔试题本身只是门槛同一套知识点在后面几轮面试里还会被反复追问。比如你在笔试里写了多线程相关的选择题二面就可能会问线程池参数你选择题里做对了TCP状态三面就可能让你画状态迁移并结合线上接口查一次TIME_WAIT问题。所以笔试复盘不能只看分数要把每道错题连到对应的知识专题里再往深挖一层。我个人体会比较深的是这套题虽然年代久远但考察的知识点框架到今天依然适用只是难度和覆盖范围水涨船高。当年能靠“熟悉集合类、会写链表反转、懂TCP流程”拿到Offer现在还得再加算法题量、分布式基础、数据库索引优化这些内容。方向比刷题量更重要把基础课按“能讲清为什么”的标准重新过一遍比闷头刷三百道题更管用。最后分享一个不算技巧但很有用的经验笔试前最好做一套完整的模拟卷按真实时间走一遍。我第一次处理这种在线笔试时节奏混乱选择题和编程题之间没有明确的时间边界最后编程题只剩三十分钟匆忙交卷。第二套模拟熟悉以后才真正稳下来。字节2017这套题难度不是最高但覆盖面很全把这份复盘吃透很多大厂后端实习的笔试节奏基本就不慌了。
返回列表