多彩编程 多彩编程MZPH · CODE BLOG
ARTICLE DETAIL

文章详情

深耕前端与后端开发技术的一线实战笔记与踩坑复盘。

力扣热题100刷题攻略:数据结构与算法核心套路解析

力扣热题100刷题攻略:数据结构与算法核心套路解析 力扣热题100这个题库很多人刷题的第一站就是它。我这两年把这100道题反复刷了两遍多加上零散练习的其他题大概有400多道今天这篇就专门聊聊怎么用数据结构与算法的基础知识去啃热题100以及我在刷题过程中踩过的坑和沉淀下来的方法。不管你是在校准备找实习还是工作几年想跳槽又或者纯粹想补一补算法内功这篇都能给你一条相对不那么痛苦的路。先说明一点刷题不是靠莽也不是靠题海战术。热题100之所以叫“热题”是因为它覆盖了面试里最高频的数据结构与算法考点而且每道题都对应一个或几个可复用的套路。把这些套路吃透比盲目刷300道新题有用得多。下面我从整体思路、数据结构拆解、算法思维、实战代码、问题排查这五个维度展开全部内容都是我根据自己和身边朋友的真实刷题经验整理的可以直接照着执行。1. 先想清楚刷题不能靠莽体系化才是正确姿势1.1 为什么“热题100”值得反复刷而不是一直追新题很多人的刷题误区是“量大于质”。我见过有人一个月刷了200道题但面试时连反转链表都写不利索原因就是每道题只做一遍AC之后就再也不看本质上只是把代码抄了一遍思维并没有升级。热题100的价值在于“高频”和“覆盖”。它不像周赛题那样偏难怪也不像算法竞赛题那样烧脑它选取的是面试官最爱考、最能体现基本功的题目。你可以把热题100理解成一个“考点大纲”数组、链表、哈希表、栈、队列、树、图、贪心、二分、回溯、动态规划、滑动窗口、双指针基本全在里面。把这100道题吃透等于把一个普通程序员三年才能踩完的算法坑提前踩了一遍。我个人建议的刷法是“三轮式”第一轮按数据结构分类刷第二轮按算法思想分类刷第三轮随机混合刷并计时模拟面试。每一轮都会看到不同的东西第一轮学套路第二轮练组合第三轮练速度和临场判断。这样刷下来你记住的不是某一道题的答案而是一类题的通法。1.2 数据结构与算法的关系学习顺序到底怎么排数据结构是载体算法是策略。这句话看着简单但很多人会搞反。比如一上来就啃《算法导论》或者硬背动态规划的状态转移方程结果代码一行都写不出来。我的建议是先过数据结构再进算法数据结构没搞明白之前算法题基本是空中楼阁。推荐学习顺序是数组、字符串和链表这三个线性结构打底——这是所有数据结构的地基然后栈、队列、哈希表——它们是辅助思想和空间换时间的工具接着树和二叉树——这是递归思维的主战场也是后续堆、图的基础最后才是堆、图、前缀树、并查集这些进阶结构。教材方面如果是为了考研408或者系统补基础严蔚敏的《数据结构》和王道的数据结构辅导书都够用但刷题时不必抱着教材啃遇到不会的知识点再回来看对应章节效率高得多。另一个容易踩的坑是“数据结构学会了但不会用”。比如栈很多人知道它是“先进后出”的但遇到“有效括号”这种题却想不到用栈去匹配。这很正常知识到应用的跨越需要刻意练习。所以我不建议把《数据结构》整本书学完再刷题而是学一种结构立刻去力扣刷对应的题学用结合印象才深。1.3 语言选择与刷题环境准备语言选型是很多新手纠结的第一个问题。力扣上C、Java、Python是主流我的建议是如果你还在校且准备找后端开发优先C或Java如果主要目的是快速刷题验证思路Python最香。但不管选哪种都要把常用API练熟比如C的vector、unordered_map、priority_queueJava的ArrayList、HashMap、PriorityQueuePython的list、dict、heapq。面试时最尴尬的不是不会做而是思路对了容器操作写不出来。环境方面我推荐“本地IDE 力扣网页”组合。力扣网页用来写题和看题解本地IDE用来跑测试和复现调试。本地环境至少要把编译器和调试器配好C选手用CLion或VS CodeJava选手用IntelliJ IDEAPython选手直接VS Code加Python插件就够了。调试器一定要会用尤其是断点查看中间变量排查数组越界、死循环的时候比print调试快十倍。还有一个细节力扣的输入输出和传统OJ不一样不需要你写完整的主函数只需要把核心逻辑放进题目给定的方法里。这其实降低了刷题门槛但也意味着你不能在本地无脑复制粘贴要学会在本地写一个简易的测试框架或者使用力扣的“自定义测试用例”功能来验证边界情况。2. 数据结构核心逐一拆解这些“底料”决定了你的上限2.1 数组、字符串与双指针最基础也最容易翻车数组和字符串是热题100中出现频率最高的结构几乎每三题里就有一题跟它们相关。数组的特点是连续内存、随机访问O(1)但插入删除是O(n)字符串的特例是字符数组还多了一个“不可变”的坑。很多新手觉得数组简单结果一写就翻车最常见的错误就是下标越界。双指针是数组和字符串最常见的解题工具。热题100里的“两数之和 II”“盛最多水的容器”“三数之和”都是双指针的经典应用。双指针的核心在于“单调性”左指针和右指针朝着某个方向移动时问题的搜索空间能不断缩小从而把O(n^2)暴力优化到O(n)。我给新手的一个建议是先把“快慢指针”和“左右对撞指针”这两类分清楚。快慢指针通常用来找中点、判环、去重左右对撞指针通常用在有序数组或字符串回文判定上。做题时先判断数组是否有序、是否要求原地操作、是否找连续子区间再决定用什么指针思路会清晰很多。2.2 链表指针操作的必修课链表题在热题100里数量不少“反转链表”“环形链表”“相交链表”“合并两个有序链表”都是高频题。链表的核心就是指针的重新指向刷题时建议在纸上画一画节点和指针的走向尤其是反转链表你只要能把“prev、cur、next”三个指针的移动画明白就掌握了一半链表题。链表的第二个坑是“哑节点”的使用。删除头节点这种操作如果不加哑节点就得单独处理头节点情况代码很容易写出一堆分支。我在刷题时总结的经验是凡是涉及删除节点、需要统一处理头节点与否的题二话不说先加一个dummy节点代码瞬间简洁很多。链表和递归的关系也值得单独说。像“反转链表”和“两两交换链表中的节点”这类题用迭代写要维护好几个指针但用递归写只要想清楚“当前节点之后已经处理好接下来把当前节点接上去”就可以。不过递归的代价是空间O(n)如果面试官要求O(1)空间还是得老老实实写迭代。所以链表题我建议迭代和递归都写一遍两手准备。2.3 栈、队列、单调栈括号、窗口背后的套路栈和队列看着简单但热题100里很多中等难度题都靠它们。“有效的括号”是栈的入门题左括号入栈、右括号出栈比对即可这个题的扩展版本是“最长有效括号”那就需要栈里存放下标来算长度难度上了一个台阶。“滑动窗口最大值”则是队列与堆的经典结合题。你如果只用一个普通队列每次滑动窗口找最大值要O(k)时间整体O(nk)超时。这里要用单调双端队列队头是当前窗口的最大值新元素入队时把队尾比它小的元素全部弹出。说起来简单写起来容易在“什么时候弹出队头过期下标”上出错我的经验是先判断队头下标是否滑出窗口再维护队尾单调性顺序不能反。单调栈也是热题100里的隐藏考点“每日温度”“柱状图中最大的矩形”都是它的代表。单调栈解决的核心问题是“找左侧或右侧第一个比当前元素大/小的位置”理解了单调栈的“入栈弹出时机”和“栈内存下标还是存值”这两个关键选择类似题目都能快速套上模板。2.4 哈希表与其他结构空间换时间的两个典型代表哈希表是热题100里出现频率最高的数据结构没有之一。“两数之和”就是哈希表的教科书“字母异位词分组”“最长连续序列”也全是哈希表的变种。哈希表的本质是用空间换时间把查找从O(n)降到O(1)代价是额外内存。刷哈希表题的通用套路是先想清楚“我要查什么”——是查元素是否存在、查出现次数还是查下标然后决定map的key和value分别放什么。堆在热题100里通常以“前K个高频元素”“数组中的第K个最大元素”的形式出现。堆的考点有两个一是堆的建立方式C的priority_queue默认大顶堆如果要小顶堆记得用greater二是堆内元素的pair比较规则很多新人会在这里卡编译错误。我个人的习惯是需要“最大/最小的K个”这种场景先想到堆再想到快速选择两者对比复杂度后选方案。图结构在热题100里虽然占比小但“课程表”这类拓扑排序题是很多人的噩梦。拓扑排序的核心是维护每个节点的入度把入度为0的节点入队逐个处理后更新相邻节点入度。这个套路背下来配合BFS就能解决大部分依赖关系类问题。2.5 树与二叉树递归思维的主战场二叉树是热题100的另一个重头戏“二叉树的中序遍历”“最大深度”“翻转二叉树”“验证二叉搜索树”“二叉树的最近公共祖先”等我几乎在每次面试题单里都能看到。树的题十有八九用递归因为树本身就是递归定义的。递归写树的题核心是搞明白“这道题要让每个节点做什么”和“递归返回值代表什么”。比如“最大深度”递归函数的语义就是“返回以当前节点为根的子树的最大深度”那么当前节点的深度自然等于左右子树最大深度加一。这个思路一清楚代码十行以内写出来。“验证二叉搜索树”是最容易掉坑的题。很多人只检查了每个节点的左孩子小于父节点、右孩子大于父节点但忽略了整个左子树都必须小于根节点。正确做法是给递归函数传一个上下界范围比如用long型最小值负无穷和最大值正无穷来初始化。这个坑我亲眼见很多刷了上百题的人踩过因为测试用例里常常会埋伏边界值。树和图的代码模板要多积累。层序遍历用队列、前中后序用递归或显式栈、求路径和用DFS回溯这些模板记牢之后热题100里树相关题目基本属于“默写”状态。3. 算法思维重点突破二分、回溯、动态规划到底怎么练3.1 二分查找边界条件是二分题的魂二分查找本身很简单但热题100里的“搜索旋转排序数组”“在排序数组中查找元素的第一个和最后一个位置”都能把人绕晕。二分的核心不是“数组有序”而是“每次搜索都能排除一半”所以很多非典型有序数组也能用二分。写二分最容易错的三个地方循环条件写left right还是left rightmid怎么算要不要加一以及区间更新时left mid还是left mid 1。我的模板是用左闭右闭区间即[ left, right ]循环条件是left rightmid left (right - left) / 2避免越界。当nums[mid] target时left mid 1否则right mid - 1。这个模板能解决八成的二分查找题。“搜索旋转排序数组”需要你先判断mid落在左侧有序区间还是右侧有序区间再决定往哪边搜。我第一次做这题时完全懵后来总结成一句话先根据nums[left]和nums[mid]的关系判断哪半边有序再判断target在不在有序半边里。掌握了这个套路类似“寻找旋转排序数组中的最小值”也能顺手解掉。3.2 回溯算法把暴力枚举优雅化的三板斧回溯算法在热题100里主要对应“全排列”“子集”“组合总和”“括号生成”这类题。回溯的本质就是DFS状态回退说人话就是走一步、试试看、不行就退回来走另一条路。它和暴力枚举唯一的区别是暴力枚举不剪枝回溯会在递归过程中剪掉不符合要求的分支。写回溯的模板非常固定先画递归树再确定每一层可以选择的元素然后用一个路径变量记录当前选择递归进入下一层后必须撤销选择。大部分回溯题的框架就是“选择列表 路径 结束条件”三段式。比如“全排列”结束条件是路径长度等于数组长度选择列表是“还没有被用过的元素”所以需要一个visited数组。回溯题最常问的复杂度其实很高比如全排列是O(n * n!)子集是O(n * 2^n)面试官其实清楚这类题不能真暴力关键是你能不能通过剪枝来优化。比如“组合总和”里先排序然后遇到和超过target就break这就是最常见的剪枝操作。3.3 动态规划从暴力递归到状态转移的思维跃迁动态规划是热题100的天花板也是大多数人刷题路上的分水岭。“爬楼梯”“不同路径”“最大子数组和”“买卖股票的最佳时机”是入门四件套“编辑距离”“正则表达式匹配”则是进阶代表。学习DP我建议走一条三步路径第一步先用暴力递归写出“尝试”版本递归函数的参数就是状态第二步加一个记忆化数组把重复计算的中间结果存下来这就是自顶向下DP第三步把递归改成迭代从边界状态开始向后推也就是自底向上DP。走完这三步你对“状态”“转移方程”“初始状态”的理解会非常牢固。很多新手直接背状态转移方程结果就是换一道题又不会。我个人的习惯是“先定义清楚dp[i]的含义再写转移方程”。比如“最大子数组和”dp[i]如果定义为“以第i个元素结尾的最大子数组和”转移方程就是dp[i] max(nums[i], dp[i-1] nums[i])整个逻辑就非常自然。这个“定义优先”的习惯让我后面做“最长递增子序列”“零钱兑换”时都少走了很多弯路。3.4 排序、贪心与字符串匹配的隐藏考点排序算法虽然在热题100里不会直接考察手写快排但“颜色分类”“合并区间”“根据身高重建队列”这些题都会用到排序思路。热题100里的快排、归并、堆排要求你必须能写出来尤其是归并排序在“排序链表”里还是官方标准解法之一。我建议把所有排序算法都手写一遍包括冒泡、选择、插入、快排、归并、堆排知道它们的时间复杂度和稳定性这对面试综合考察特别有用。贪心算法在热题100里往往以“跳跃游戏”“任务调度器”等题出现。贪心的难点在于证明“局部最优就是全局最优”但面试时通常不需要严格证明你只要能从直觉上解释清楚为什么这么贪心即可。比如“跳跃游戏”只需要维护能到达的最右位置每一步都更新这个位置就行如果最右位置一直能超过数组末尾就返回true。字符串匹配算法在热题100里不算主流但KMP算法的思想值得掌握热词里反复出现KMP。KMP的核心是“前缀函数”用next数组记录模式串的前缀和后缀的最长公共部分这样主串指针不用回退时间复杂度能到O(mn)。这类硬核算法不建议作为刷题起步内容等你把热题100刷到60题以上再回头补也不迟。4. 热题100实战拆解从读题到AC的完整路径4.1 两数之和第1题哈希表优化的第一课这大概是力扣历史上AC人数最多的题给你一个整数数组nums和一个目标值target找出和为目标值的两个整数的下标。暴力解法是两层循环O(n^2)对于大数组必然超时。思路推导我每遍历到一个数x都需要知道“之前有没有出现过target-x”。如果已经把之前的数都存进哈希表key是数值、value是下标那么查一下target-x在不在哈希表里就行。这一步就直接把查找从O(n)降成O(1)整体复杂度O(n)。代码实现Cclass Solution { public: vectorint twoSum(vectorint nums, int target) { unordered_mapint, int mp; for (int i 0; i nums.size(); i) { int need target - nums[i]; if (mp.count(need)) { return {mp[need], i}; } mp[nums[i]] i; } return {}; } };这道题我觉得最有价值的点不是哈希表本身而是“先查再存”的顺序。有些人喜欢先把所有元素存进哈希表再遍历这样遇到重复元素时下标会互相覆盖还要额外处理。直接边遍历边存天然避免了这个问题也符合“我需要的是当前元素之前出现过的数”这个语义。这个处理顺序在后来的“最长连续序列”“字母异位词分组”里也反复出现值得形成肌肉记忆。4.2 无重复字符的最长子串第3题滑动窗口的教科书题目给一个字符串s要求找出其中不含重复字符的最长子串长度。暴力思路是枚举所有子串再用set判断重复复杂度O(n^2)甚至O(n^3)显然不行。思路推导用左指针和右指针构成一个滑动窗口窗口内保证没有重复字符。右指针不断向右扩展并加入新字符如果新字符在窗口内重复就不断右移左指针直到窗口重新合法。因为左右指针都只向前移动每个字符最多被访问两次复杂度O(n)。代码实现Cclass Solution { public: int lengthOfLongestSubstring(string s) { unordered_setchar window; int left 0, ans 0; for (int right 0; right s.size(); right) { while (window.count(s[right])) { window.erase(s[left]); left; } window.insert(s[right]); ans max(ans, right - left 1); } return ans; } };写这道题时最容易犯的错是“重复字符判断用set还是用map”以及“什么时候更新答案”。我的经验是只关心“有没有重复”时用set就够了如果要优化到O(1)维护“每个字符最后出现的位置”可以用map但set版代码更好理解面试优先讲set版。ans的更新要在while循环结束后因为此时窗口才真正合法。我第一次写就把ans放在了while之前导致结果偏大这个调试点我今天都记得。4.3 最长回文子串第5题中心扩展法的直观解法要求找出字符串s中的最长回文子串。回文串就是正着读反着读一样热题100里这道题常被用来考察“能不能想到回文串从中心向两边扩展”。暴力枚举所有子串再判断回文O(n^3)不可行。动态规划可以做dp[i][j]表示s[i..j]是否为回文状态转移是dp[i][j] (s[i] s[j] dp[i1][j-1])复杂度O(n^2)但需要O(n^2)空间。另一种更简洁的是中心扩展法对每个位置i分别以s[i]为中心奇数长度和以s[i]、s[i1]为中心偶数长度向两边扩展记录最长长度。复杂度同样是O(n^2)但空间O(1)代码量也小。代码实现Cclass Solution { public: pairint, int expand(const string s, int left, int right) { while (left 0 right s.size() s[left] s[right]) { left--; right; } return {left 1, right - 1}; } string longestPalindrome(string s) { int start 0, maxLen 1; for (int i 0; i s.size(); i) { auto [l1, r1] expand(s, i, i); auto [l2, r2] expand(s, i, i 1); if (r1 - l1 1 maxLen) { start l1; maxLen r1 - l1 1; } if (r2 - l2 1 maxLen) { start l2; maxLen r2 - l2 1; } } return s.substr(start, maxLen); } };这个题最容易被忽略的是奇数长度和偶数长度两种情况都要考虑。比如abba这种偶数回文以单个字符为中心永远扩展不出正确结果。我身边有不少朋友卡在这个点上其实只要记住“对每个位置尝试两种中心”这一句话就能解决。另外C里返回pair有点麻烦如果你在面试现场直接写两个独立的扩展函数会更好读。4.4 编辑距离第72题二维动态规划从推公式到写代码给你两个单词word1和word2可以插入、删除、替换一个字符计算把word1转换成word2所需的最少操作次数。这道题是热题100里的压轴DP也是面试官真正用来拉开差距的题。状态定义dp[i][j]表示把word1的前i个字符转换成word2的前j个字符所需的最少操作数。初始条件dp[0][j] j表示从空串变成word2前j个字符需要插入j次dp[i][0] i表示从word1前i个字符变成空串需要删除i次。转移方程当word1[i-1] word2[j-1]时dp[i][j] dp[i-1][j-1]否则dp[i][j] min(dp[i-1][j-1], dp[i][j-1], dp[i-1][j]) 1。其中dp[i-1][j-1]1是替换dp[i][j-1]1是插入dp[i-1][j]1是删除。这个方程我建议用表格手动推一遍比如word1horse到word2ros推完整个表格你会对DP有质的理解。代码实现Cclass Solution { public: int minDistance(string word1, string word2) { int m word1.size(), n word2.size(); vectorvectorint dp(m 1, vectorint(n 1, 0)); for (int j 0; j n; j) dp[0][j] j; for (int i 0; i m; i) dp[i][0] i; for (int i 1; i m; i) { for (int j 1; j n; j) { if (word1[i - 1] word2[j - 1]) { dp[i][j] dp[i - 1][j - 1]; } else { dp[i][j] min({dp[i - 1][j - 1], dp[i][j - 1], dp[i - 1][j]}) 1; } } } return dp[m][n]; } };编辑距离最常考的三类追问空间能不能优化可以只需要两行滚动数组因为dp[i]只依赖dp[i-1]和dp[i]本身能不能同时输出具体操作序列可以回溯dp表能不能处理不同操作代价不同的情况可以把1换成对应代价就行。我建议每做一道DP题都想一遍这三个追问这对面试现场很有帮助。4.5 合并K个升序链表第23题优先队列与分治的对比给你一个链表数组每个链表都已经按升序排列把所有链表合并成一个升序链表。暴力方案是把所有节点取出排序再串成一个链表O(n log n)但面试官通常希望你能写出更“算法化”的解法。方案一优先队列最小堆。把K个链表的头节点放进一个最小堆每次弹出值最小的节点追加到结果链表中然后把这个节点的下一个节点加入堆。因为堆的插入和弹出都是O(log K)总的复杂度是O(n log K)n是所有节点总数。方案二分治合并。两两合并链表第一轮合并后剩K/2个链表第二轮再合并以此类推。单次合并两条链表的复杂度是O(mn)总的复杂度也是O(n log K)但不需要额外堆空间不考虑递归栈。代码实现优先队列Cclass Solution { public: ListNode* mergeKLists(vectorListNode* lists) { auto cmp [](ListNode* a, ListNode* b) { return a-val b-val; }; priority_queueListNode*, vectorListNode*, decltype(cmp) pq(cmp); for (ListNode* head : lists) { if (head) pq.push(head); } ListNode dummy(0); ListNode* cur dummy; while (!pq.empty()) { ListNode* node pq.top(); pq.pop(); cur-next node; cur cur-next; if (node-next) pq.push(node-next); } return dummy.next; } };这道题我建议大家两种方案都写一遍。优先队列版本能考察你对堆API的熟悉度分治版本能考察你对递归和链表合并的掌握度。面试时先讲清楚两种方案的复杂度对比再手写一种基本上就能过关了。另外一个容易踩的坑是不要把K个链表的所有节点一次性全塞进堆里那和排序就没有区别了维护K个当前最小值才是这个解法的灵魂。5. 刷题过程中最常见的坑与排查技巧实录5.1 超时了怎么办三步自查复杂度力扣提交后显示“Time Limit Exceeded”是新手最常见的挫折。我遇到超时后会按三个步骤自查第一步看自己解法的时间复杂度如果数据范围是10^5而你的算法是O(n^2)那基本必超时老老实实优化思路第二步确认是不是提交前忘了去掉调试打印别笑print/debug代码忘记删导致超时的案例我有好多次第三步如果复杂度看起来没问题检查有没有死循环尤其是while循环里指针没有递增的场景。数据范围这个习惯要养成。力扣题目下方通常会有1 nums.length 10^5这样的提示看到10^5就要意识到O(n)才安全O(n log n)勉强可以O(n^2)直接换思路。这比死记“数组题要用双指针”靠谱多了因为你是在按数据规模做工程决策。5.2 递归爆栈与内存溢出怎么排查递归爆栈的典型报错是“Stack Overflow”树和DFS的题特别容易遇到。如果你的递归深度能达到10^5以上比如退化链表形态的二叉树遍历或者要递归一千万层就需要改成显式栈迭代。C和Java默认递归栈空间都不大Python更明显。内存溢出则多和数组开太大有关。我见过有人为了求“最长连续序列”直接开一个10^9的布尔数组在力扣上肯定过不了。正确做法是用哈希表只保存存在的元素再只从“可能成为序列起点的元素”开始查找这样空间复杂度降到O(n)。碰到内存问题第一反应不是缩小数组而是反思能不能用哈希表或字典代替离散的大数组。5.3 看题解就懂合上就忘复习策略比题量重要“看题解秒懂合上书就忘”这个问题几乎人人都有不用怀疑自己。人类的记忆遵循遗忘曲线所以刷题一定要设计复习机制不能只做“一遍过”。我的个人习惯是“三色标记法”AC且一次过、思路顺畅的题打绿色AC但调试了很久或看了题解的题打黄色完全没思路需要看题解才能写出来的题打红色。红色题隔2天重做黄色题隔7天重做绿色题隔30天抽查这样滚动下来每个知识点都会被反复激活记忆牢固度会高很多。另外我强烈建议你给每一道题写下“一句话复盘”不是长篇大论而是类似“用滑动窗口关键是在窗口内维护字符频率”这样的句子。复盘写的是思路和卡点不是代码写多了你会发现自己的难点高度集中比如就集中在“边界条件”和“状态定义”这两处。5.4 时间不够用学生党和上班族的刷题节奏很多粉丝问我“每天只有1小时怎么刷题”我的答案是一小时足够但要把这一小时拆成“30分钟新题20分钟复习旧题10分钟写复盘”。新题不求多一天一题或两题就够关键是彻底搞懂。在此基础上可以按星期设主题周一数组双指针、周二链表、周三哈希表、周四树、周五DP、周末做综合模拟。如果你是赶在面试前突击我的建议是“只刷热题100的高频标记题”优先掌握树、哈希表、双指针、滑动窗口、DP这五类。它们占面试题的八成以上。那些冷门题比如正则表达式匹配硬骨头可以往后放不要在最需要出效果的时候浪费时间啃硬骨头。5.5 沉淀输出笔记、题解、代码库如何整理刷题刷到后面真正拉开差距的是“能不能把题讲明白”。我见过不少刷题多的人代码写得好但面试时讲不清楚思路这类人很容易在面试官追问下翻车。所以从第一天刷题起就要有“输出”的心态每道题试着用三句话说清思路一句话说清复杂度再模拟面试官问自己两个为什么。技术方案上建议用GitHub仓库或博客把题解按标签分类整理文件结构可以是LeetCode/Category/题号_题名.md里面记录题目描述、核心思路、代码、复杂度、卡点和同类题链接。这个仓库不仅仅是给你自己复习用的也是你简历上的一个亮点。我在面试时被问到算法项目直接打开自己的刷题仓库很多面试官都对这个习惯印象很好。还有一个值得养成的习惯是做“一题多解”记录。热题100里面很多题都至少有两种主流解法比如“最大子数组和”可以DP也可以分治“数据流中的中位数”可以用双堆也可以用排序。每道题多记录一个解法你的武器库就会越来越丰富遇到变种题时能调用的招数自然比只记一种解法的人多。聊到这我想起自己刚刷热题100时第一遍几乎有一半题目是看题解才写出来的那时候特别沮丧。后来想通了刷题本质上是一个“刻意练习”的过程重要的不是第一遍会不会而是会不会做复盘、能不能在下一次遇到同类题时想起来用过这个套路。我个人的体会是当你把热题100刷完两轮并且认认真真写了复盘笔记之后再去看其他题库的题大部分题目你都能一眼看出考点因为万变不离其宗。最后再分享一个小技巧别只盯着题解里的最优解先把自己的暴力思路写出来再去比较优化方案差在哪这种“由劣到优”的对比过程才是能力提升最快的地方。
返回列表