1. 试卷全景扫描:一份2017年的笔试卷,藏着哪些技术风向标
先说结论:这套搜狐2017秋招研发工程师笔试试卷(一),整体难度在当年互联网公司校招笔试卷里属于中等偏上。和同期百度、阿里的题目相比,搜狐这套卷子更偏重基础功底的扎实程度,没有特别偏怪难的竞速题,但陷阱题密度不小,尤其在一些概念细节上,如果不仔细推敲,很容易丢分。
先说说这套卷子的基本盘。从我搜集到的信息来看,试卷结构大致是选择题(单选+多选)加编程题的组合,覆盖了计算机网络、操作系统、数据结构与算法、Java/C++语言基础、数据库等校招笔试常考的几大板块。题量大概在40到50道之间,考试时间两个半小时左右,这个节奏意味着平均每题只有3分钟出头,对于需要读题、计算、排除干扰项的题目来说,其实不算宽裕。
为什么这套卷子值得拿出来复盘?我的看法是,它很好地代表了2017年前后互联网公司校招笔试的典型出题风格:重基础、考细节、看思维。现在很多同学准备秋招时喜欢刷各种LeetCode难题、偏题,反而忽略了学校课堂里那些最基础的概念,而这套卷子恰好给了我们一个提醒——笔试真正筛掉的,往往不是不会做难题的人,而是基础概念模棱两可、一看就会一写就错的人。
另外,从题目设计的角度看,这套卷子还有一个值得注意的特点:它喜欢把两个容易混淆的知识点放在同一个题目里做对比,比如TCP的TIME_WAIT状态和CLOSE_WAIT状态的区别、进程和线程在资源开销上的区别、HashMap和Hashtable的线程安全性问题,这种出题方式比单纯问"什么是TCP三次握手"要高明得多,因为它考察的是你是否能在实际工程场景中做出正确选择,而不是背概念。
这篇复盘文章,我会按模块拆解这套卷子的核心题目,逐个分析考点、给出解题过程,再补充一些我在实际面试辅导中遇到的常见错误和避坑经验。无论你是正在准备校招的应届生,还是工作几年想回头补基础的在职开发,这篇文章应该都能帮你理清一份经典笔试卷背后的考察逻辑。
2. 计算机网络模块:TCP状态机与HTTP细节,永远是校招笔试的"送分题"与"送命题"
2.1 TCP四次挥手的状态转换:TIME_WAIT为什么是2MSL
搜狐这套卷子的网络部分,有一道关于TCP连接关闭过程的题目,考察的是主动关闭方在发送最后一个ACK之后进入的状态。答案是TIME_WAIT,这个大多数人能答对,但后面跟了一个追问——为什么TIME_WAIT要等待2MSL(Maximum Segment Lifetime,报文最大生存时间)?这道题能完整答出来的人,我估计不到三成。
先说说2MSL的来历。MSL是TCP报文在网络中存活的最长时间,RFC 793里建议的默认值是2分钟,但实际Linux系统中通常设置为30秒到1分钟。主动关闭方在发送完最后一个ACK之后,并不能立刻关闭连接,而是要等待2MSL的时间,原因有两个层面。
第一个原因,也是最容易理解的原因:确保最后一个ACK能到达对端。TCP是可靠传输协议,但ACK报文本身也是IP报文,也可能在传输过程中丢失。如果主动关闭方的最后一个ACK丢了,被动关闭方会因为收不到ACK而超时重传FIN报文,如果主动关闭方已经关闭了连接,那它收到重传的FIN后只能回复RST,导致对端报错。等待2MSL,就是为了给可能丢失的ACK留出重传和重新确认的时间窗口。
第二个原因,是让网络中属于这个连接的所有旧报文全部消失。一个报文从发出到到达对端,最长不会超过MSL,再加上回程的MSL,2MSL可以保证一个方向上的报文和对应ACK在网络中彻底消失,防止下一个使用相同四元组(源IP、源端口、目的IP、目的端口)的新连接收到旧连接的残留报文,造成数据错乱。
这道题我见过不少同学的错误回答,有人说"等待对端关闭连接",有人说"确保数据全部发送完毕"。这两种说法都不准确。TIME_WAIT不是被动等待对端做什么,而是主动方自己需要等待的一个时间窗口。理解到这个层面,才算是真正吃透了TCP状态机。
2.2 HTTP状态码与缓存机制:301/302/304的工程区别
网络模块另一道值得拎出来说的题,是关于HTTP状态码的。题目给了一个场景:浏览器访问某个URL,服务器返回304 Not Modified,问浏览器接下来会怎么做。正确答案是浏览器会使用本地缓存的资源副本,不会重新下载资源。
这题本身不难,但它连着考察了HTTP缓存机制的几个关键概念:ETag、Last-Modified、Cache-Control。实际工程中,304的使用场景非常普遍,尤其是静态资源(图片、CSS、JS文件)的加载优化。我见过一些刚工作的开发者在排查线上问题时,发现服务器日志里一堆304请求,以为是异常,其实这恰恰是缓存生效的正常表现。
这里要区分一下301和302。301是永久重定向,浏览器会缓存这个重定向结果,下次访问直接跳到新地址,不再请求原地址;302是临时重定向,浏览器每次都会先请求原地址,拿到响应后再跳到新地址。还有一个容易混淆的是303和307,这两个都是"使用GET请求重定向",但303明确要求把POST改为GET,307则保持原请求方法不变。这些细节在RESTful API设计、单点登录跳转等场景里非常容易踩坑。
对于备考的同学,我建议把HTTP状态码按类别记,不要死记硬背数字。2xx表示成功,3xx表示重定向,4xx是客户端错误,5xx是服务端错误。每个类别记住几个代表性的就够了,重点是理解状态码背后的语义,而不是背数字。搜狐这道题就是在考察你有没有真正理解304的语义,而不是看到304就一脸懵。
2.3 TCP拥塞控制:慢启动、拥塞避免、快重传、快恢复的协作逻辑
这套卷子还有一道关于TCP拥塞控制的题,涉及慢启动阶段cwnd(拥塞窗口)的增长方式。慢启动阶段,每收到一个ACK,cwnd增加一个MSS(最大报文段长度),所以每经过一个RTT(往返时间),cwnd翻倍,呈指数增长。当cwnd达到ssthresh(慢启动阈值)后,进入拥塞避免阶段,每个RTT只增加一个MSS,线性增长。
之所以把慢启动设计成指数增长,是因为TCP在连接刚建立时并不知道网络的可用带宽,需要快速探测。如果一开始就按线性增长,在高带宽延迟乘积的网络里可能要花很长时间才能占满带宽;但如果指数增长不收住,又会很快导致网络拥塞,所以需要ssthresh这个阈值来切换增长模式。
快重传和快恢复是后来加入的优化机制。快重传的意思是说,如果发送方连续收到3个重复的ACK,就认为某个报文丢失了,不等超时计时器到期,立即重传丢失的报文,这样可以避免不必要的等待。快恢复则是把ssthresh减半,同时把cwnd设为新的ssthresh值,进入拥塞避免阶段,而不是回到慢启动重新探测。这里要注意一个细节:传统TCP Tahoe实现遇到丢包会直接回到慢启动,而TCP Reno引入快重传和快恢复后,性能有了明显提升,这也是这道题考察的核心区分点。
我在面试中经常问候选人一个问题:如果网络里频繁出现丢包,你会优先调整哪些TCP参数?很多人的第一反应是调大缓冲区,但正确的思路是先分清楚丢包的原因——是缓冲区溢出、网络拥塞、还是链路质量问题。不同原因对应的优化手段完全不同,这在笔试里不会考,但在实际工作中才是真正重要的能力。
3. 操作系统模块:进程线程与内存管理,这些题看似简单,坑却不少
3.1 进程和线程的经典对比题:资源共享与切换开销
操作系统模块,搜狐出了一道非常经典的对比题:关于进程和线程,下列说法正确的是哪个。选项涉及到进程是资源分配的基本单位、线程是CPU调度的基本单位、同一进程内的线程共享地址空间、进程切换的开销大于线程切换等。
这里面最容易出错的是最后一个选项。理论上说,同一进程内的线程切换确实比进程切换开销小,因为线程切换不需要切换地址空间,不需要刷新TLB(页表缓存)。但这里有个前提:如果两个线程属于同一个进程。如果是不同进程的线程,切换开销和进程切换基本一样,因为地址空间还是要切换。很多同学在做题时忽略了这层限定条件,看到"线程切换开销小于进程切换"就直接选,这就是典型的审题不仔细。
还有一个相关的坑:进程是资源分配的基本单位,线程是CPU调度的基本单位,这句话在绝大多数教材里都有,但有些人会记反。资源分配指的是内存空间、文件描述符、信号处理器这些资源的归属,进程是这些资源的所有者;而CPU调度关心的是"接下来执行哪段指令流",线程是执行流的最小单位,所以线程是调度的基本单位。
另外这道题的选项里可能还涉及一个细节:进程地址空间里的线程私有部分是什么。每个线程有独立的栈空间、寄存器上下文、线程局部存储(TLS),但堆空间是共享的。在做题时,要分清楚"共享"和"私有"的边界,这也是多线程编程的基础。
3.2 死锁的四个必要条件:为什么互斥条件无法取消
关于死锁,这套卷子有一道典型的判断题,考察死锁的四个必要条件:互斥、占有且等待、不可剥夺、循环等待。题目问的是"通过破坏哪个条件可以预防死锁",或者"下列哪个条件被破坏后死锁一定不会发生"。
我最想展开说说的,是互斥条件这一点。很多同学刚接触死锁时会想:既然互斥是死锁的必要条件之一,那把互斥取消了不就行了吗?但实际工程中,互斥条件几乎是无法取消的——两个线程同时往同一个文件里写数据、同时更新同一个数据库记录、同时操作同一个全局变量,这些场景天然就是互斥的,你不可能为了让它们不死锁就允许它们同时写,那会导致更严重的数据不一致问题。所以现实中的死锁预防,通常集中在破坏后三个条件上:比如用"一次性申请所有资源"来破坏占有且等待条件,用"资源编号+按序申请"来破坏循环等待条件,用"超时回退"来破坏不可剥夺条件。
还有一个容易混淆的概念:死锁和饥饿。死锁是多个线程互相等待对方释放资源,谁都无法推进;饥饿是一个线程一直得不到资源,但其他线程可以正常推进。两者表现相似,但本质不同。笔试中如果只给了"某个线程一直无法执行"这个描述,很多人会误判为死锁,但正确答案可能是饥饿。
我建议备考的同学把死锁相关的内容整理成一个对比表格:死锁的四个必要条件、对应的预防策略、对应的避免策略(银行家算法)、以及死锁检测与恢复机制。把这四层搞清楚,操作系统部分的死锁题基本就稳了。
3.3 页面置换算法:LRU和FIFO的失效次数对比
内存管理方面,搜狐考了一道页面置换算法的题,给了访问序列和物理块数,让计算LRU和FIFO各自的缺页次数。这类题只要细心模拟,一般不会错,但要注意两个细节。
第一个细节是算法的定义。FIFO(先进先出)置换最先进入内存的页面,实现简单但可能出现Belady异常(物理块数增加,缺页次数反而增加);LRU(最近最久未使用)置换最长时间没有被访问的页面,利用局部性原理,性能一般优于FIFO,但需要记录访问时间信息,实现成本更高。题目如果问"哪种算法可能出现Belady异常",答案是FIFO。
第二个细节是初始状态。做题时要注意题目给的物理块最开始是否为空。如果为空,前几个页面加载时一定会产生缺页中断,这些也算在缺页次数里。我见过不少同学在这个地方漏算,导致最终答案差了几次,非常可惜。
另外想多说一句:LRU在硬件上的实现其实不是用链表,而是用近似算法。现代CPU的页表项里有一个访问位(Access Bit),操作系统定期检查这些位,把没被访问过的页面换出去,这个算法叫Clock算法,也叫第二次机会算法。它是LRU的近似实现,因为真正的LRU需要在每次内存访问时更新全局信息,开销太大。这道题虽然只考了LRU和FIFO的基础计算,但其实现代操作系统里用的都是近似算法,理解这一点对后续学习内核很有帮助。
4. 数据结构与算法:从二叉树到动态规划,校招笔试的中流砥柱
4.1 二叉树的遍历组合:为什么知道中序+前序就能重建二叉树
数据结构部分,搜狐考了一道关于二叉树遍历的题,问的是:已知一棵二叉树的前序遍历序列和中序遍历序列,能否唯一确定这棵二叉树。答案是能。更进一步,如果只给前序和后序呢?答案是不能唯一确定。
原因其实很简单。中序遍历的特点是"左子树-根节点-右子树",有了前序遍历后,第一个节点就是根节点,然后拿着这个根节点去中序遍历里找它的位置,左边是左子树、右边是右子树,然后递归地对左右子树做同样的操作,就能重建整棵二叉树。前序和后序组合为什么不行?因为前序(根-左-右)和后序(左-右-根)都能确定根节点,但当某个节点只有一个子节点时,前序和后序无法区分这个子节点到底是左孩子还是右孩子,所以会出现歧义。
这道题在笔试里通常不只是考判断题,还会让你写出重建过程或者分析时间复杂度。重建算法的时间复杂度是O(n^2)的朴素实现,如果用哈希表预处理中序遍历中每个节点的位置,可以把单次查找降为O(1),整体优化到O(n)。我在实际面试中看到不少候选人能把递归写法写出来,但很少有人主动讲到哈希表优化这一步,这其实是个很好的加分点。
另外,搜狐的题目有时候会把二叉树和数组结合起来考,比如给定一个数组,判断它是不是某棵二叉搜索树的后序遍历结果。这类题看起来是二叉树,本质上是递归分治,每次把数组分成左子树、右子树和根三部分,验证左子树都小于根、右子树都大于根即可。
4.2 排序算法的时间复杂度与稳定性:快排为什么不是稳定排序
排序算法的考察在这套卷子里也有,具体题目是关于快速排序在最坏情况下的时间复杂度。快速排序的平均时间复杂度是O(n log n),但在每次划分都极端不平衡、比如数组已经有序的情况下,时间复杂度会退化为O(n^2)。这一点是基础中的基础,但我发现很多同学会忽略"最坏情况"这四个字,看到快速排序就直接选O(n log n),丢分丢得很冤。
稳定性的判断也是高频考点。稳定排序的含义是:如果两个元素的值相等,排序后它们的相对位置不变化。稳定排序有冒泡排序、插入排序、归并排序;不稳定排序有选择排序、快速排序、堆排序、希尔排序。快排之所以不稳定,核心原因在于划分操作中的交换可能会把相等的元素交换到另一边,破坏相对顺序。比如数组[3a, 3b, 1],以最后一个元素1为基准划分时,3a和3b都会被交换到右边,排序后3b和3a的顺序就反转了。
这里我给备考的同学一个建议:不要死记硬背哪些排序稳定、哪些不稳定,而是理解每种排序的核心操作——交换、插入、选择、归并——对相等元素相对位置的影响。理解了原理就不容易记混。
4.3 动态规划:从一道经典题看状态转移方程的设计思路
算法编程题方面,这套卷子有一道典型的动态规划题,考察方向是最长公共子序列或者类似的经典DP模型。我不确定搜狐当年具体用了哪道题,但这一类题目的解题方法论是通用的,值得展开讲透。
动态规划的核心是状态定义和状态转移方程。以最长公共子序列(LCS)为例,定义dp[i][j]表示字符串A的前i个字符和字符串B的前j个字符的最长公共子序列长度。状态转移方程分两种情况:如果A[i]等于B[j],那么dp[i][j] = dp[i-1][j-1] + 1;否则dp[i][j] = max(dp[i-1][j], dp[i][j-1])。这个方程的含义是:要么当前两个字符匹配上了,在之前的基础上加一;要么当前字符匹配不上,那就取去掉A一个字符或去掉B一个字符后的最优解。
为什么这道题是校招笔试的高频题?因为它考察的不只是代码能力,而是建模能力——你能不能把一个问题抽象成"状态+转移"的模型。这种能力在真实的工程开发中同样重要。我在带新人时经常说,写业务代码其实也需要建模:一个订单状态机、一个任务调度流程,本质上都是状态和转移的问题。
对于动态规划的代码实现,我建议统一用迭代+数组的方式,不要用递归+备忘录,因为迭代方式不容易爆栈,而且空间优化(滚动数组)也更方便。LCS的空间复杂度可以从O(n*m)优化到O(min(n,m)),方法是用两行数组滚动更新,因为计算dp[i]只依赖dp[i-1]这一行的数据。
5. 编程语言与数据库:Java/C++的陷阱题和SQL查询的细节
5.1 Java的String不可变性:为什么"a"+"b"和new String("ab")不一样
编程语言题目中,搜狐考了Java String类的不可变性。String在Java中被设计为不可变类,一旦创建就不能修改。每次对String做拼接、替换、截取操作,都会生成新的String对象,而不是在原有对象上修改。这也是为什么在循环中拼接字符串时,应该用StringBuilder而不是直接用加号——否则每次循环都会创建新的String对象,造成大量垃圾回收压力。
String不可变的设计有几个好处:字符串常量池可以安全地共享相同内容的字符串;String作为HashMap的键时,哈希值可以缓存,提高查找效率;字符串对象可以被安全地用于多线程环境,不需要额外的同步。这道题如果考到"为什么String是不可变的",从这三个角度回答就比较完整。
还有一个常见的混淆点是String和StringBuilder、StringBuffer的区别。String不可变,StringBuilder和StringBuffer可变;StringBuilder线程不安全但性能好,StringBuffer通过synchronized保证线程安全但性能略差。单线程环境下优先用StringBuilder。这些细节在校招笔试里几乎年年出现,建议备考的同学一定要分清楚。
5.2 C++的指针与引用:声明时的一个符号差异,决定了完全不同的语义
C++题目方面,搜狐考察了指针和引用的区别。指针是一个变量,存储的是另一个变量的地址,它可以重新赋值指向其他变量,也可以为nullptr;引用是另一个变量的别名,必须在声明时初始化,而且之后不能改变指向。这在函数参数传递场景中的影响非常大:传值会拷贝整个对象,传指针和传引用都能避免拷贝,但传指针需要判空、调用时要用取地址符&,传引用则更安全、语法更简洁。
这里有一个笔试中高频出现的细节:引用在声明时必须初始化,指针没有这个要求。所以"int &ref;"是编译错误的,但"int *ptr;"是完全合法的。另外一个细节是sizeof运算符的结果:对一个指针取sizeof,得到的是指针本身的大小(64位系统下是8字节);对一个引用取sizeof,得到的是被引用对象的大小。这两个点在选择题里经常作为陷阱出现。
C++还有一个常考的概念是深拷贝和浅拷贝。默认的拷贝构造函数做的是浅拷贝,如果类里有指针成员,浅拷贝会导致两个对象指向同一块内存,析构时出现double free的问题。正确做法是实现自定义拷贝构造函数和赋值运算符重载,做深拷贝。搜狐的C++题目如果有问"为什么需要自定义拷贝构造函数",就是为了考察这一点。
5.3 SQL查询:GROUP BY与HAVING的配合,JOIN的三种区别
数据库模块,搜狐考了一道SQL查询题,我记得比较清楚的一个点是考察GROUP BY和HAVING的配合使用。WHERE是在分组之前对原始记录进行筛选,HAVING是在分组之后对聚合结果进行筛选。所以要查"部门人数大于10的部门",不能写成WHERE COUNT() > 10,而应该用HAVING COUNT() > 10。这个基本概念虽然简单,但在实际笔试中错误率极高,因为很多同学记不清两者的执行顺序。
JOIN的考察也是必选项。LEFT JOIN返回左表的全部记录,如果右表没有匹配,右表字段为NULL;RIGHT JOIN相反;INNER JOIN只返回两边都匹配的记录。还有一个容易被忽略的细节是:JOIN之前可以用WHERE对左表或右表做过滤,但过滤条件放的位置会影响结果。比如LEFT JOIN时,如果WHERE条件限定右表的某个字段不为空,那这个LEFT JOIN实际上就退化成INNER JOIN了。这是一个非常经典的SQL陷阱题,我在面试中也经常拿来考察候选人对JOIN语义的理解深度。
在实际工作中,SQL优化是每个后端开发都会遇到的话题,笔试虽然只考语法层面,但理解背后的执行逻辑——什么时候走索引、什么时候全表扫描、子查询能否改写成JOIN——对于写出高性能SQL至关重要。建议备考的同学把常见SQL的执行顺序记住:FROM -> WHERE -> GROUP BY -> HAVING -> SELECT -> ORDER BY -> LIMIT,这条链路理解了,基本不会在逻辑题上犯迷糊。
6. 逻辑题与智力题:笔试卷里的"隐形分拣器"
6.1 经典逻辑题的类型拆解:从称球问题到推理题
2017年的校招笔试卷子,普遍还保留着一部分逻辑推理题,搜狐这套卷子也不例外。这类题在大学计算机课程里不会教,但笔试中却占有一定比例,通常是几道选择题的量。称球问题、真假话问题、排座位问题、烙饼问题,都属于这类,考察的是逻辑推理能力和数学建模能力。
以称球问题为例:有12个球,其中1个重量异常(不知道偏轻还是偏重),用天平称3次找出这个球。这道题看似简单,实际上是个信息论问题。天平每次称量有3种结果:左重、右重、平衡,3次称量最多有3^3=27种结果,而12个球、每个球可能偏轻或偏重,共有24种可能性,信息论上刚好够用。解题时需要精心设计分组策略,每次称量的分组要保证三个分支覆盖的情况数尽可能平均。
逻辑推理题在笔试中的作用是快速筛选出具备清晰思维能力的候选人。对准备校招的同学,我的建议是不要花太多时间刷这类题,因为投入产出比不高。重点还是要把数据结构和算法的基础打牢,逻辑题只要掌握几类经典模型、能保证中等难度以下的不丢分即可。
6.2 数学期望题:笔试中的概率与统计基础
还有一类题也经常出现在这套时期的笔试卷里:概率和期望。比如"两个人在一个小时内随机到达某个地点见面,每人等15分钟,问两人能见面的概率"。这类题看起来是脑筋急转弯,实际上是几何概型的应用,把到达时间作为x轴和y轴,把"能碰面"的条件转化为一个线性不等式,然后求面积比例。
对于这类题,正确的做法是养成画图的习惯。凡是涉及两个独立随机变量的概率题,都可以尝试用坐标平面上的面积来求解,这是最直观也最不容易出错的方法。有的同学喜欢用积分硬算,但几何概型往往能帮助一眼看出答案,大大节省做题时间。
这套卷子里还出现过一道关于硬币抛掷的期望题,问连续抛硬币直到出现正面的期望次数。标准解法是设期望为E,第一次抛有两种情况:正面(概率1/2,次数为1)和反面(概率1/2,需要重来,期望为1+E),所以E = 1/2 * 1 + 1/2 * (1+E),解出E=2。这种"期望的递推方程"解法是概率题的通用方法,建议熟练掌握。
7. 一套笔试卷背后的备考方法论:写给正在准备校招的你
复盘完这套搜狐2017秋招研发工程师笔试试卷(一),我想说几句超出题目本身的体会。
这套卷子虽然出自2017年,但它的题型分布和考察逻辑在今天的校招笔试中依然有很强的参考价值。你去看现在各大厂的笔试题,考的核心依然是这些:计算机网络、操作系统、数据结构与算法、编程语言基础、数据库,外加一部分逻辑和概率题。题目可能会更新,但底层的能力要求没有变——扎实的计算机基础、清晰的逻辑思维、熟练的代码能力。
针对这类笔试,我建议备考的同学建立一个"知识清单"式的复习框架,按模块整理自己掌握的知识点,每个知识点都要能达到"能给别人讲明白"的程度。如果你发现自己解释不清楚TCP的TIME_WAIT状态为什么需要2MSL、讲不明白HashMap的扩容机制和死循环问题、写不出一个二分查找的边界条件,那说明这个知识点还没有真正掌握,做题时大概率会在相关题目上翻车。
另外,做题的速度和准确率需要刻意训练。笔试的时间限制非常严格,我建议备考期间每套题都按真实考试的时间来卡,给自己制造紧迫感。做完之后不能只看对错,要把每道题的考点、错因、相关知识点整理成错题本。很多同学刷题几百道却不见提升,就是因为只在意数量,没有把每道题背后的知识点真正吃透。
最后再分享一个小习惯:笔试前,把TCP状态转换图、操作系统进程状态转换图、各种排序算法的时间复杂度和稳定性表、HTTP常见状态码含义这些"背诵型"内容集中整理在一张纸上,考前快速过一遍。这些东西决定了你的下限分数,而算法题则决定了你的上限。保证下限、争取上限,笔试题的分数自然不会低。
从搜狐2017年的这套卷子里,我们能清楚地看到校招笔试的出题逻辑:它不追求考生掌握多么冷门的知识,而是反复检验计算机专业最核心的基础功底——那些大学四年里反复出现、工作中每天都在使用的基础概念和算法思维。把这些基本功练扎实,比刷一百道冷门偏题要有用得多。