字节跳动程序媛教你如何刷算法题:面试手撕代码我就没怕过

2年前 (2022) 程序员胖胖胖虎阿
242 0 0

文章目录

    • 写在前面
    • 来,我们发车!
      • 首先,你知道手撕代码为什么那么重要吗?
      • 面试手撕代码的规律是什么,和一般的算法题有啥区别?
      • 题海茫茫,题库千万。我该如何选择?
      • 到底该怎么刷题,才能算是真正掌握这道题呢?
        • 一、刷题前准备
        • 二、刷题时思考
        • 三、刷题后总结
      • 咳咳,那干货这不就来了?
      • 手撕代码超级好用的小tips~
    • 写在最后

写在前面

“题海茫茫,leetcode上千道。我该怎么刷题?”
“题目刷完了,面试时手撕代码还是脑袋空空,怎么办?”
你在准备春、秋招面试算法题是不是为这两个问题迷茫过?

那么恭喜你,你发现了这篇宝藏文章。本文将给你指明一条刷题的决胜指南。并从算法的重要性、题库的选择以及刷题的三大步骤分别进行讲解,彻底解决你所有关于面试算法所有的困扰。

温馨提示,本文不会教你如何提升刷题水平,也没有奔着蓝桥杯、ACM等各类算法大赛破纪录的标准冲刺。作者本人就是一个智商平庸的程序员,没有什么说的出口的竞赛经验,上一次刷题还是大一的C语言期末考试……但是面试中算法考核截止上岸前一直保持着100%正确率的光辉记录。

字节跳动程序媛教你如何刷算法题:面试手撕代码我就没怕过
本文的目的纯粹而真实:教你刷最少的题,用最少的时间,最稳妥的方法拿下面试算法题!

如果你的代码能力平平的新手小白,那么这篇文章一定能让你受益匪浅;如果你已经蝉联各种金奖银奖,那就当网络一线牵,相逢即是缘。看在这场缘分上,点个赞再走呗~

好了,废话说到这里。

来,我们发车!

算法,作为校招乃至社招最重要的考核标准之一,也是考核通过一决生死的关键凭据。是各位候选人准备面试的“重头戏”。

首先,你知道手撕代码为什么那么重要吗?

因为算法能力可以看出应聘者的计算机基础能力。这里的基础能力包括:扎实的编程能力、良好的代码习惯以及解决问题的思维能力。

你可能会不屑一顾,算法在实际工作中真的有用吗?

说句实话,非常有用又非常“没用”。
没用在于在你真正入职之后,繁忙的业务和紧促的需求可能让你根本没有时间去思考各种算法如何应用,直接大把大把可复用代码copy;但正因为这些“不经思考”的代码泛滥成灾,一个对于代码严谨性、鲁棒性高标准、严要求的人才就成为了面试官眼里的“香饽饽”。

一个能够挽救这堆“垃圾代码”的人简直就是上帝呀!

字节跳动程序媛教你如何刷算法题:面试手撕代码我就没怕过
所以,算法能力对于长期的需求迭代和工程架构的稳定性而言,是非常非常重要的,也是程序员从普通的搬砖码农晋升到架构师必须具备的能力。
既然它那么重要,作为面试的核心评判标准,不过分吧?

接下来,你可能又好奇了:一恩姐姐,为什么你前面说,这篇文章不能提升算法能力,但能拿下手撕代码呢?
问得好,让我来告诉你们:

面试手撕代码的规律是什么,和一般的算法题有啥区别?

首先给各位分享一个宝藏真题网站:https://github.com/afatcoder/LeetcodeTop,github的star已破12k(2022.02),它是我见过的最全面最好用的面试算法真题汇总。

它的题源来自于牛客网面经笔经、CSDN以及热心网友分享,真实可靠,并且实时保持更新,目前共收录了811道真题(仍在持续增长中)。支持按公司、部门、岗位组合查询仓库中的数据,方便大家高效检索,针对性应对面试。

进入首页我们便能看到它按照考察的频度由高到低对算法题进行排序。面试爱考啥一览无余。

字节跳动程序媛教你如何刷算法题:面试手撕代码我就没怕过
通过对这个网站题型的剖析,我们非常清晰地可以总结出以下三条规律:

  • 数据结构偏好:数组&哈希表 > 字符串&链表 > 二叉树&图 > 其他;
  • 难度偏向于简单、中等类;
  • 考察密度较为集中;

下面分别详细解释一下这三点:

  1. 数组&哈希表 > 字符串&链表 > 二叉树&图 > 其他;

我做了一个简单的统计,该网站自2020年1月开始统计一共811道算法题,其中数组&哈希表占226道(25.7%),字符串占95道(10.8%),链表占102道(11.6%),二叉树、图占120道(13.6%),栈&队列占84道(9.5),还有一些杂七杂八的数学题和设计题占76道(8.6%)。

这么一算下来已经包揽了互联网80%的题型。而我上述列出来的题型其实都是大家本科时学到的最最基础的数据结构。甚至没有排序算法、BFS/DFS,更别提什么贪心算法动态规划了。

所以,比起去死磕那20%将脑细胞杀光的题型,首先要将这80%的分值稳稳拿到手。等到基础分稳操胜券了,我们便可以去心安理得地锦上添花了。

  1. 题目偏向于简单、中等类;

同样用数据说话,难度题只占103道(11.7%)这说明什么呢?

大厂算法题对题目难度要求不高。面试总共1小时左右时间,算法考查最多也只能占30min左右的时间。对于难度大的题目,大部分候选人基本无法保证在这么短时间内给出一个正确解法。因此较为简单直接的题型成为考核的不二选择。

  1. 考察密度较为集中;

大家可以观察一下这些高频题目最近考察时间,是不是就是昨天,前天,甚至基本所有题都在一周内被反复考过。这说明啥?说明面试官的题库高度重合且更新频率低!

有一个关系不错的面试官小哥哥跟我说,他这一年面试只考过3道题,翻来覆去反复考。“方便呀”,他懒懒地说,“平时工作那么忙,也没太多时间研究那么多题型。一般会提前花点时间搞透几道经典题型就用上了,等以后有空再换吧。”看起来这个老哥的“有空”是迟迟未到。

所以呢,高频题的生命周期是永久的,是经久不衰的,是反复利用的。这个公司考到的题说不定换一家公司还会考。好好琢磨高频题是一个非常省时省力的小技巧。

说了那么多,帮大家总结一下吧。优先看 高频的、难度中等偏低的、较基础的数据结构的题型
答应我,这一步先确保做好,完完全全不需要动脑,只需要稍微努力就能成功战胜70%的面试算法。

当然了,也不是说到此为止了。选择了去支持一恩姐姐的你们,智商肯定是胜于常人的(手动狗头)剩余的30%还得是要努努力好好争取的。

下面就来说说具体的刷题方法吧?

题海茫茫,题库千万。我该如何选择?

现在还在困惑这个问题吗?别愁了,有这个时间我都刷了2道题了。就看三:

  • 剑指Offer(一定要配上书!)
    • 题库地址:https://www.nowcoder.com/exam/oj/ta?tpId=13
    • 《剑指Offer》地址:https://blog.csdn.net/monk1992/article/details/96438796
  • LeetCode
    • 题库地址:https://leetcode-cn.com/problemset/all/
  • 公司真题
    • 牛客整理的公司真题:https://www.nowcoder.com/exam/company
    • 其实我更喜欢在讨论区翻一些小伙伴们的面经题刷:https://www.nowcoder.com/discuss

资源的位置也贴在上面了,大家在牛客网上都可以直接搜到。我其实非常推崇大家直接用牛客网题库刷题,它的IDE和界面很大程度上还原了真实面试环境。大家提前熟悉一下“考场氛围”对于当天面试的手感和心态都很有帮助~

我刷题的主要输出是剑指Offer,LeetCode和公司真题都是随意打打辅助。对于剑指,我是每一道题都认真刷了至少3遍,刷后仔细总结、举一反三。而LeetCode主要是会针对一些弱点题型进行专项突破,没有去系统地刷。

同时在准备某家公司面试前一周,我会提前三四天每天刷一份该公司的算法真题,主要是用来碰运气,万一这两个面试官正好好兄弟互抄题库呢?

字节跳动程序媛教你如何刷算法题:面试手撕代码我就没怕过
现在开始说最重要的点的了,

到底该怎么刷题,才能算是真正掌握这道题呢?

我们分三个阶段一一讲解,分别是:刷题前、刷题时和刷题后;

一、刷题前准备

大家一定要抛弃一个错误观念,刷题绝对不是为了那毫无意义的数目和排名。而是为了去掌握一类题型、去巩固数据结构。因此比起去说“我昨天又刷了10道题”,我更愿意去说“我昨天把双向链表这一类问题搞透了。”

所以在刷题的时候一定要刷经典题,要举一反三,要反复刷,从一类题抽象出共通的解法,才能以不变应万变,这才是刷题的本质,而不是单单为了刷题而刷题。

有了这样一个大方向之后,我们第一步要做的就很明确:分类以及确定优先级。按照数据结构的类别进行分类就好啦,比较现成的分类可以直接按照《剑指Offer》这本书的章节来刷;而优先级则可以根据上面总结出的题型规则进行排序。我做了一个分类大概是:

哈希表与字符串 > 链表 > 二叉树与图 > 二分查找与二叉排序树 > 栈、队列、堆 > 其他(主要是一些数学问题)> 递归、回溯与分治 & 贪心算法 > 搜索 > 复杂数据结构 > 动态规划

二、刷题时思考

刷题我有自己的一套组合拳,现在来给大家拆动作讲解一番。

首先刷题一定是有针对性地刷,看完题目后请各位先来一个灵魂三问:

  1. 这道题属于哪类题型?
  2. 这类题型的解法是什么?
  3. 有没有模板可以套?

明确后,就拿出模板直接套。这里的模版是你在刷完题后对这类题型总结的模版,具体的总结方法下面小节会提到。

如果一套命中,那么恭喜你,你总结的模板是通用且合适的;如果发现没有模板或解题思路也不用着急。此时就当作面对一道新的算法题并尝试用自己学过的算法知识解决。

第一次写题不用对解题速度过于苛刻,可以给自己2~3小时时间慢慢摸索最好的方式。

时间一过,无论你有没有解出来,都应该结束这道题了。接下来你要做的就是去看看别人的解法和思路。如果你是在牛客网刷题,可以直接点击左上角的“题解”。

字节跳动程序媛教你如何刷算法题:面试手撕代码我就没怕过

一般点赞最多的是最佳的解法,我们要去看看别人的代码好在哪里,和自己的代码有什么区别。主要是从解题的思路、时间、空间复杂度等角度切入,以及是否能从别人代码中获取一些通用的启发。

站在巨人的肩膀上,才能看得更远。

在吸取他人精华后,需要再刷一遍题。这一遍刷题主要是去证实你是否真正理解并掌握了最佳解法。这一遍成功AC后,你这道题才算是真正的“刷”完。

三、刷题后总结

这一步应该是最关键的一步。

我从学生时代就一直秉承着总结是将知识真正化为己用的最核心的方法。即使到了工作阶段,依然非常受用。

对于面试算法的总结,我会去按题型进行分类,并且对于每一类题型我都会总结出一套伪代码。
每每刷完一题,我便将这道题所总结出的思路归纳到这类题型下:

  1. 首先看下这个思路与伪代码的契合度。如果无法直接套用,有2个原因。1是伪代码不够通用,2是着实属于一道新题型;
  2. 如果是1,需要根据总结出的思路进一步完善自己的伪代码;
  3. 如果是2的话,可以作为这一类题型的一个衍生题型,单独去记忆。这里的记忆不是去背题,而是去总结另一套模版。

可能有的人会好奇伪代码怎么写,我这里教大家一个通用的方法:既然不好抽象解法,我们抽象题目。将题目去简化为一个知识点,然后针对这个知识点写算法。

给大家举一个例子:
“滑动窗口算法”类题型可以用于解决最小覆盖子串、找到字符串中所有字母异位词、无重复字符的最长子串类问题。

我总结了一套滑动窗口算法的抽象思想,可以以不变应万变:

int left = 0, right = 0;

while (right < s.size()) {
    window.add(s[right]);
    right++;
    
    while (valid) {
        window.remove(s[left]);
        left++;
    }
}

总结的思路详细写在这篇文档里,大家速速进来学习一下:https://blog.csdn.net/qq_29966203/article/details/100070662

有时候我会发现自己对于某一类题无法总结出模版,或一直卡壳在一类题时。我就会专门抽出一定的时间专项攻克。这个时候我就会拿出题海战术:LeetCode。因为LeetCode题实在太多了,应有尽有。我会在LeetCode上搜索该类题直接一个猛刷的大操作,相信在千锤百炼后,脑部和手部的记忆也会给你一套自己的方法论。

看到这里,如果你对总结仍然有点云里雾里,不知如何下手。

咳咳,那干货这不就来了?

毕竟宠粉如一恩姐姐为各位上岸可是倾心倾力,操碎了心。
字节跳动程序媛教你如何刷算法题:面试手撕代码我就没怕过
我在总结过程中,按照《剑指Offer》的目录分成了十二类,每一类我都从剑指和LeetCode选择了几道极具有代表性的题型(也可以说是知识点型题)并写出了对应的解法,每道题后面标注了LeetCode/剑指上对应的题号,方便大家查询并自行练习。

家人们,直接冲就完事儿了。

第一章 链表:https://blog.csdn.net/qq_29966203/article/details/100496138

  • leetcode
    • 例1:链表逆序(206)
    • 例1b:链表逆序2(92)
    • 例2:链表求交点(160)
    • 例3:链表求环(141)
    • 例4:链表划分(86)
    • 例5:复杂链表的复制(138)
    • 例6:2个排序链表归并(21)
    • 例7:K个排序链表归并(23)
  • 剑指offer
    • 例1:从尾到头打印链表(3)
    • 例2:链表中倒数第k个节点(14)
    • 例3:反转链表(15)
    • 例4: 合并两个排序链表(16)
    • 例5: 复杂链表的复制(25)
    • 例6: 两个链表的第一个公共节点(35)
    • 例7: 孩子们的游戏(圆圈中最后剩下的数)(45)
    • 例8:链表中环入口节点(54)
    • 例9:删除链表中重复节点(55)

第二章 栈、队列、堆:https://blog.csdn.net/qq_29966203/article/details/100550574

  • leetcode
    • 例1 使用队列实现栈(225)
    • 例2 使用栈实现队列(232)
    • 例3 包含min函数的栈(155)
    • 例4 合法的出栈队列(946)
    • 例5 简单的计算器(224)
    • 例6 数组中第K大的数(215)
    • 例7 寻找中位数(295)
  • 剑指offer
    • 例1:用两个栈实现队列(5)
    • 例2:包含min函数的栈(20)
    • 例3:栈的压入、弹出序列(21)

第三章 贪心算法:https://blog.csdn.net/qq_29966203/article/details/94612334

  • leetcode
    • 例1:分发饼干(455)
    • 例2:摇摆排序(376)
    • 例3:移除K个数字(402)
    • 例4a:跳跃游戏(55)
    • 例4b:跳跃游戏2(45)
    • 例5:射击气球(452)
    • 例6:最优加油方法
  • 剑指offer
    • 例1:剪绳子(66)
  • 2019校招
    • 例1:安置路灯(3)

第四章 递归、回溯与分治:https://blog.csdn.net/qq_29966203/article/details/93963493

  • 剑指Offer
    • 例1:Fibonacci数列
    • 例2:跳台阶
    • 例3:变态跳台阶
    • 例4:矩形覆盖
    • 例5:矩阵中的路径(64)
    • 例6:机器人的运动范围(65)
    • 例7:把数组排成最小的数(32)
    • 例8:字符串的排列(27)
    • 例9:数组中的逆序对
  • leetcode
    • 例1:计算右侧小于当前元素的个数
    • 例1:求子集1(原数组不包含重复元素)
    • 例2:求子集2(原数组包含重复元素)
    • 例3:括号生成
    • 例4:N皇后
    • 例5:火柴棍摆正方形(473)

第五章 二叉树与图:https://blog.csdn.net/qq_29966203/article/details/96424693

  • leetcode
    • 例1:路径之和(113)
    • 例2:最近的公共祖先(236)
    • 例3:二叉树转链表(114)
    • 例4:侧面观察二叉树(199)
    • 例5:课程安排(有向图判断环)(207)
  • 剑指offer
    • 例1:重建二叉树(4)
    • 例2:树的子结构(17)
    • 例3:二叉树的镜像(18)
    • 例4:从上往下打印二叉树(22)
    • 例5:二叉树中和为某一值的路径(24)
    • 例6:二叉搜索树与双向链表(26)
    • 例7:二叉树的深度(37)
    • 例8:平衡二叉树(38)
    • 例9:二叉树的下一个节点(56)
    • 例10:对称的二叉树(57)
    • 例11:按之字形顺序打印二叉树(58)
    • 例12:把二叉树打印成多行(59)
    • 例13:序列化二叉树(60)

第六章 二分查找与二叉排序树:https://blog.csdn.net/qq_29966203/article/details/100169820

  • leetCode
    • 例1:插入位置(35)
    • 例2:区间查找(34)
    • 例3:旋转数组查找(33)
    • 例4:二叉查找树编码与解码(449)
    • 例5:逆序数(315)
  • 剑指offer
    • 例1:旋转数组的最小数字(6)
    • 例2:二叉搜索树的后序遍历序列(23)
    • 例3:数字在排序数组中出现的次数(36)
    • 例4:二叉搜索树第k个结点(61)
  • 2019 校招
    • 例1:丰收(11)

第七章 哈希表与字符串:https://blog.csdn.net/qq_29966203/article/details/100029889

  • leetcode
    • 例1:最长回文串(409)
    • 例2:词语模式(290)
    • 例3:同字符词语分组(49)
    • 例4:无重复字符的最长子串(3)
    • 例5:重复的DNA序列(187)
    • 例6:最小窗口子串(76)
  • 剑指offer
    • 例1:替换空格(2)
    • 例2:数组中出现次数超过一半的数字(28)
    • 例3:最小的K个数(29)
    • 例4:第一个只出现一次的字符(34)
    • 例5:数组中只出现一次的数字(39)
    • 例6:左旋转字符串(42)
    • 例7:反转单词顺序序列(43)
    • 例8:把字符串转换成整数(48)
    • 例9:数组中重复的数字(49)
    • 例10:字符流中第一个不重复的字符(53)

第八章 搜索:https://blog.csdn.net/qq_29966203/article/details/100088992

  • leetcode
    • 例1:岛屿数量(200)
    • 例2:词语阶梯(127)
    • 例3:词语阶梯2(126)

第九章 动态规划:https://blog.csdn.net/qq_29966203/article/details/95489974

  • leetcode
    • 例1:爬楼梯(70)
    • 例2:打家劫舍(198)
    • 例3:最大字段和(53)
    • 例4:找零钱(322)
    • 例5:三角形(120)
    • 例6:最长上升子序列(300)
    • 例7:最小路径和(64)
    • 例8:地牢游戏(174)
  • 剑指offer
    • 例1:连续子数组的最大和(30)
    • 例2:滑动窗口最大值(63)
  • 2019校招真题
    • 例1:牛牛找工作(1)
    • 例2:牛牛的背包问题(8)

第十章 复杂的数据结构

第十一章 其他:https://blog.csdn.net/qq_29966203/article/details/100658666

  • leetcode
    • 例1:倒转二进制(190)【位运算】
  • 剑指offer
    • 例1:二维数组的查找(1)【数组、智力】
    • 例2:二进制中1的个数(11)【位运算】
    • 例3:数值的整数次方(12)【代码的完整性】
    • 例4:调整数组顺序使奇数位于偶数前面(13)【代码的完整性】
    • 例5:顺时针打印矩阵(19)【画图让抽象形象化】
    • 例6:整数中1出现的次数(31)
    • 例7:丑数(33)
    • 例8:和为S的连续正数序列(40)
    • 例9:和为S的2个数字(41)
    • 例10:扑克牌顺子(44)
    • 例11:1+2+3+…+n(46)【条件与&&短路原则】
    • 例12:不用加减乘除做加法(47)【位运算】
    • 例13:构建乘积数组(50)【数组】
    • 例14:正则表达式匹配(51)【模拟】
    • 例15:表示数值的字符串(52)【模拟】
    • 例16:数据流中中位数(62)
  • 2019校招
    • 例1:被3整除(2)【数学】
    • 例2:迷路的牛牛(4)【模拟】
    • 例3:数对(5)【数学】
    • 例4:重叠矩阵(6)【数学】
    • 例5:牛牛的闹钟(7)【日期】
    • 例6:俄罗斯方块(9)【模拟】
    • 例7:瞌睡(10)【模拟】

上面精炼出的约120道左右算法题可谓是题题经典。
每一道题我都刷了至少3~4遍,总结出的模板能让我有十足的底气地应对所有面试题。与此同时,针对临考前这短暂时间如何高速高效抱佛脚,我将上面的算法汇总进一步浓缩再浓缩,总结了一个精简版算法总结与归纳,共50题不到。基本每一次面试前我都会花个大约半小时的时间将里面的算法模型迅速过一下脑子,保持一个代码的手(脑?)感。
目录我就不列出来了,大家点进去看吧。每一道题已经都属于一个核心必考知识点了。
算法总结与归纳
https://blog.csdn.net/qq_29966203/article/details/102745404

以及因为总结出来的题目实在太重要了,于是我又双叒叕将这些算法题打印出来了,每天背一背,快乐养乐多~
字节跳动程序媛教你如何刷算法题:面试手撕代码我就没怕过
字节跳动程序媛教你如何刷算法题:面试手撕代码我就没怕过
我还会在每次面试完后,针对该场面试的手撕代码重新巩固与批注自己的笔记体系。所以打印出来的笔记才会密密麻麻一大片,有的地方记不下了就贴上了便利贴……

还得多说一句,总结真的是一个非常好的学习习惯,他能够将知识真正化为己用,成为自己的东西。各位在看完一恩的资料后,一定要自己总结一份属于自己的资料,归纳出自己的模板。相信拥有一份属于自己的武林秘籍后,无论面对什么样的算法题,都能邪魅狂狷一笑“呵,就这?”

好了,刷题的方法也说的差不多了,最后来一点我在经历过各路面试总结出的一些

手撕代码超级好用的小tips~

  1. 首先无论有没有面试,都要保证一天刷2~3题,时间不要长,每道题最多给自己半小时。然后看题解,总结。刷不刷出来都不要太在意,关键是培养一个手感。
  2. 在面试前过一遍自己总结的核心算法题和思路,确保模版的套路是牢记于心且应用自如的
  3. 当面试官让你手撕代码时,要注意:
  4. 一定一定不要急着写!就算你题目听一半就发现这道题你刷过,也不要惊喜地直接冲上去public void main!谦虚、低调一点好吗?这个时候,你要发挥出你毕生的演技,装模作样皱眉思考一会儿,然后问问面试官自己的思路对不对,达成共识后再落笔;
  5. 敲代码的过程一定要注意工整的代码格式和书写习惯。面试过程中你敲的每一行代码都赤裸裸地暴露在面试官眼下,身材是好是坏一看就看出来了;
  6. 对于一些陌生或没有思路的题型一定不能焦虑也不能干坐着,主动地说说自己思路或与面试官讨论。面试官一般会给一些提示,这些提示往往是很关键的,而且是一个转折点。毕竟敲出来就有希望,敲不出来一般都凉凉了。

写在最后

面试的手撕代码,相对于你们平时参加各种比赛的算法题,最大的不同在于它要简单太多。很多人认为他难,其实并不是题目难,而是在面试这个一对一的氛围下所营造出的一种紧张、窒息的氛围会让原本你还不错的代码能力大打折扣,很多题。所以一定要保持一颗好的心态,同时要多刷题,尽量用惯性代替脑子。防止出现一些难以应对的意外。

最后如果这篇文章对你有帮助的话,记得点一个赞或收藏~一恩姐姐祝所有点赞的小可爱都能成功上岸哟~

字节跳动程序媛教你如何刷算法题:面试手撕代码我就没怕过

相关文章

暂无评论

暂无评论...