ARTICLE DETAIL

资讯详情

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

LeetCode刷题总结:二分查找模板与热门题复刷策略

LeetCode刷题总结:二分查找模板与热门题复刷策略 做LeetCode刷题的人很多但真正把题目转化成自己能力的人并不多。我见过太多人刷了三百题遇到新题还是没思路也见过一些人只刷了一百来题却能应付大部分面试算法环节。差别不在数量在于有没有形成自己的知识总结体系。这篇是我这个系列的第24篇照例不写流水账只聊那些真正值得反复消化的知识点和题目背后的套路。1. 为什么总结比刷题更决定上限1.1 刷题数量不等于掌握程度先说一个我观察了很久的现象。很多人刷题的方式是打开题解看明白思路自己敲一遍通过下一题。这种流程走完五十题之后会有一个明显的错觉——这些题我都见过。但真到面试或周赛时换个壳就认不出来了。我自己的体会是一道题做三遍并总结规律比做三道不同的题收获更大。第一遍是认识解法第二遍是脱离题解独立完成第三遍是隔一两周后回来复现并且顺手把这个题归入自己的知识框架。这个归入框架的动作才是总结的核心。这里分享一个很实用的指标如果一道题你做完之后能在三天后不看任何资料用五分钟之内讲清楚这道题的思路、时间复杂度和关键边界条件那这道题才算真正消化了。讲不清楚的基本都是当时背下来的而不是理解的。1.2 知识点的归类方式决定检索效率很多人觉得总结就是抄一遍题解或者记个博客其实不然。有效的总结方式应该像整理书架一样按主题放书而不是按时间顺序堆书。我习惯把LeetCode知识点分成这么几个大方向数据结构类数组、链表、栈、队列、哈希表、堆、树、图算法思想类二分、双指针、滑动窗口、回溯、动态规划、贪心、分治技巧类位运算、前缀和、差分、单调栈、并查集、拓扑排序每做完一道题我先问自己一个问题这道题如果让我给一个初学者讲思路我会先讲数据结构还是先讲算法思想答案一般就是这道题的主标签。然后再往上挂一到两个副标签比如哈希表 滑动窗口、二分 贪心判断。这样做的价值在于当你遇到新题时大脑检索的路径是这题像是哪一类而不是我之前做过哪道有点像的题。前者是结构化记忆后者是零散记忆。面试时你就能感受到这个差异有多关键。2. 二分查找从爱吃香蕉的狒狒看到通用的三种考法2.1 一道经典题的完整拆解热点里提到了073爱吃香蕉的狒狒这是LeetCode 875题可以说是二分查找应用题的经典代表很多公司面试都喜欢拿它当热身题目。题目本身很简单一堆香蕉每一堆数量不同狒狒一小时吃一堆但可以选择吃多少根。给定总时间H求最小的每小时吃香蕉速度K。这道题暴露了两个典型的二分误区。第一个误区是有人一上来就想对数组本身做二分——实际上这里二分的对象是吃的速度这个值域跟香蕉数组本身无关。第二个误区是搞不清楚左边界和右边界怎么收敛容易在边界条件上死循环。我当时做这道题时第一反应也是直接模拟从速度1开始往上试计算每个速度是否能在H小时内吃完。这样做当然能出结果但K最大可以到十的九次方级别逐个试效率太低根本过不了。正确做法是对K做二分。K的最小值是1最大值可以设置为香蕉堆中最大的那一堆数量因为速度超过最大值没有意义一小时只能吃一堆。每次取中间值写一个判断函数模拟狒狒以这个速度吃完所有香蕉需要多少小时。判断函数的核心很直接每一堆需要的时间是(pile K - 1) / K向上取整。如果总时间小于等于H说明这个速度可行但可以试试更小的速度所以收缩右边界否则收缩左边界。这个能行就试试更小的二分思路很多人写着写着就绕晕了。我自己的记忆口诀很简单判断函数返回 true 时说明当前值满足条件但不一定是最优解所以往更优的方向继续找。在这种找最小可行值的题目里更优的方向就是左半边。2.2 二分查找的三种考法对应关系这题做完之后我回头整理了一下LeetCode里关于二分的考法发现可以归成三类考法一是经典二分搜索直接在一个有序数组中找目标值这是最基础的题型。比如在排序数组中查找元素的第一个和最后一个位置本质上就是写两个二分一个找左边界一个找右边界看似基础写起来却很容易出错。考法二是答案值域二分。就是上面的狒狒题这类题目不给一个明确的数组让你搜索而是给一个问题让你在可能的答案范围里找一个边界值。之前很流行的分割数组的最大值也是这类题我做的过程中最大的感受是这类题的核心难点不在二分逻辑本身而在于判断函数的设计。判断函数写不好二分框架再熟练也没用。考法三是在隐含单调性上二分。这种情况最隐蔽题目里没有明说有序但仔细分析后能发现单调性。比如求一个数的平方根看起来跟二分没关系但数字范围本身天然有序。又比如一些第K小元素的问题通过对值域二分用计数器统计比当前值小的元素个数再逐步逼近答案。2.3 我沉淀下来的二分模板因为这类题做多了我整理了一个自己的二分模板基本覆盖了80%以上的场景def binary_search(nums, target): left, right 0, len(nums) - 1 while left right: mid left (right - left) // 2 if nums[mid] target: return mid elif nums[mid] target: left mid 1 else: right mid - 1 return -1这个模板最重要的是mid left (right - left) // 2这一步。我见过有人直接写(left right) // 2当数字很大时会溢出。虽然LeetCode的测试用例一般不会卡这个点但养成好习惯总没坏处。而对于答案值域二分我的模板是这样的def feasible(mid): # 根据题目设计判断逻辑返回是否可行 pass left, right min_val, max_val while left right: mid (left right) // 2 if feasible(mid): right mid else: left mid 1 return left这个模板里while left right和right mid、left mid 1的搭配是找最小可行值的标准写法。如果题目变成找最大可行值就把逻辑反过来用left mid和right mid - 1。记住这两套写法后绝大多数问题都能套进去。注意这类题真正烧脑的是 feasible 函数。做狒狒这题时feasible 只需要模拟吃香蕉过程但做第K小类题目时feasible 可能就是一个双指针或者堆的应用。所以刷二分时与其把时间花在背框架上不如多练几道不同场景的 feasible 设计。3. 热门100题里最有复刷价值的是这几类3.1 看似简单却藏满细节的题LeetCode热门100题是很多人的必刷清单。但我的经验是热门题不等于简单的题更不等于做完就能会的题。就拿两数之和来说大部分人用暴力解过了就不再回头看了。但如果你换个角度思考——为什么可以用哈希表核心原因是查找一个与当前值互补的数这个动作哈希表可以做到O(1)的查找。把一个O(n平方)的问题降到O(n)这就是数据结构改变算法复杂度的最典型案例。这类藏满细节的题我特别推荐重点关注这样几道两数之和哈希表空间换时间的思想启蒙接雨水双指针、单调栈、动态规划三种解法都能做思路跨度极大LRU缓存机制哈希表双向链表的结构设计题面试高频合并K个升序链表优先队列的典型应用每一道都值得反复刷三遍以上。就拿LRU来说我在面试中见过不少候选人能说出哈希表双向链表这个答案但真到手写代码时节点删除、头尾指针更新这些细节一写就乱。原因就是练得太少只看懂了思路没有形成肌肉记忆。3.2 热门题里的思维升级路径热门100题的价值我认为不在于题目本身难度而在于它们构建了一条思维升级的路径。比如你做完了两数之和再看三数之和会发现排序双指针这个思路其实是对暴力解法的一种聪明剪枝你做完三数之和再做四数之和又会发现完全可以将三数之和的解法封装起来套一层循环而已。这个套壳升级的规律在动态规划里更典型。从爬楼梯到打家劫舍再到最长递增子序列本质上都是定义状态、找转移方程。真正理解了这种递进关系刷题就会轻松很多。所以我一直建议刷热门100题时不要按题号顺序刷按主题递进刷。先把哈希表相关的刷完再刷双指针相关的再刷动态规划的。这样你能感受到知识之间的关联而不是孤立地记每一道题的答案。3.3 我重刷热门题的节奏安排关于重刷我自己有一个很具体的节奏策略分享出来供参考第一遍刷的时候不追求独立写出代码但要求理解解法并能口述思路。第二遍安排在两周之后这一遍要求独立写出代码不看题解。第三遍安排在面试前或周赛前快速过一遍每道题只给自己十分钟能AC就过不能AC就标记为薄弱题然后专项重练。这样做下来热门100题里真正需要第三遍的人可能只有30道左右这30道就是你的盲区所在。三轮下来你对这些题的理解深度和背答案完全不是一个级别。4. LeetCode周赛430竞赛题暴露的常见知识盲区4.1 周赛题型分布与备战策略周赛430的题解是最近的热词我也去打了这场。说实话周赛和日常刷题最大的区别在于日常刷题你有充足时间慢慢想周赛却是在时间压力下逼你做出取舍。这个取舍能力恰恰是面试中最实用的能力。以我的经验来看周赛的四道题通常是这样分布的Q1签到题往往是考基本功的比如模拟、字符串处理基本是5-10分钟内要搞定Q2稍微需要一点思考的题可能是哈希表、贪心或者简单DP15分钟左右要搞定Q3开始上难度经常涉及数据结构或者更复杂的思维可能需要二三十分钟Q4综合压轴题经常是图论、高级数据结构或者复杂的动态规划很多高手也可能卡住备赛策略上我的做法很固定每周三开始看本周题目相关的知识点提前预热周赛当天提前静坐几分钟让脑子进入状态打比赛时先快速过一遍四道题判断哪些是硬骨头合理分配时间。4.2 周赛430里让我印象深刻的卡点周赛430这场我复盘时发现自己卡在了一道中等难度的题目上原因很典型题目描述看起来像个模拟题但实际上需要的是一个数据结构来优化时间复杂度。我一开始按模拟思路写写了大几十行还各种边界出错后来意识到应该用堆来维护一个有序结构换了一个思路代码量反而少了一半。这个经历很有代表性。它反映了竞赛题的一个常见套路题干里给的约束条件往往暗示了正确的解法方向。比如数据范围到达十的五次方一般就不可能再用O(n平方)的暴力解法如果涉及频繁取最大值或最小值多半用堆如果涉及区间和考虑前缀和或树状数组如果是处理括号匹配或嵌套结构优先想栈。我把这个根据数据范围推断算法的思路总结成了自己的选型表数据范围可接受的复杂度常用思路n ≤ 20O(2ⁿ)状态压缩、回溯n ≤ 1000O(n²)双重循环、动态规划n ≤ 10⁵O(n log n)排序、二分、堆、树状数组n ≤ 10⁶O(n)哈希表、双指针、滑动窗口这张表虽然不绝对但作为预判方向非常管用。周赛里时间就是金钱如果一开始就选对了算法就算实现过程中有些小问题整体节奏也会从容很多。4.3 从周赛中提取可复用经验的方法打完周赛后最重要的一步是复盘。我的复盘不只是看一下题解而是给自己三个问题的回答第一这四道题里哪一道是我原本可以做出来却没有做出来的原因是什么是思路没对上还是实现细节出了问题第二哪一道是我花了太长时间这部分时间能不能通过更好的选型省下来第三有没有哪道题用到了一种我此前没见过的技巧如果有把这道题归类并记录下来。做完这三步才算是把一场周赛消化透了。我见过一些人打完周赛只看排名排名涨了就开心排名掉就沮丧却从不复盘。到头来打了几十场周赛水平还是原地踏步那就是最可惜的。5. 我自己在刷题总结中踩过的坑和磨出来的方法5.1 笔记记了等于没记的误区我早期刷题时也写过很详细的笔记每道题都抄一遍题解配上思路解析写完之后自我感觉特别好。但后来复习时发现我抄的那些东西根本没有进入我的脑子看到题还是想不起来思路。后来我反思了一下发现问题的本质在于笔记的方式太抄写化了缺少了主动输出。抄写题解时大脑是被动接收的而真正的理解需要主动重组信息用自己的语言把解题逻辑讲一遍甚至讲给一个虚拟的初学者听。所以后来我改成了题后三行法。每道题做完不抄题解只用三行字总结这道题的最优解套路、关键边界条件和这道题和自己已有知识点的关联。这三行字是逼自己想出来的不是抄来的。效果完全不一样。5.2 用标签体系替代线性笔记现在我的刷题记录是完全标签化的每个标签代表一个知识切片题目只是这个切片下的一个案例。比如单调栈这个标签下我会挂上柱状图中最大的矩形每日温度接雨水等题每道题挂上去时我都会在题目旁标注这道题的关键特征是找最近更大/更小值。这样一来复习的时候就特别高效。想看单调栈点进去一次能复习七八道题看的时候还能对比它们之间的异同加深理解。相比那种第1天到第300天每天记一道题的日志式笔记这种按知识点组织的结构不知道好用多少倍。还有一点值得提的是隔一段时间要把笔记里相似的知识点做合并整理。比如滑动窗口和双指针看似是两种思路其实很多题既可以用双指针也可以用滑动窗口多做几道你就会发现它们之间的关联边界这时候合并成一个大标签反而思路更清晰。5.3 时间分配上的一个具体建议最后聊一下刷题的时间分配这也是我踩坑踩出来的经验。大部分人刷题是有时间就狂刷几小时没时间就一周不碰这样的节奏效果最差。因为算法的思维模式是需要持续保持的间断太久再回来会非常生涩。我更建议的模式是每天固定四十分钟到一小时雷打不动。前十五分钟复习昨天的错题或写一道之前做过的题中间三十分钟做一道新题最后五分钟整理笔记和标签。这样一天一天积累下来你实际投入的总时间可能不比那些周末猛刷的人少但掌握程度会扎实很多。6. 一套可以复制的刷题总结迭代流程在写了二十多篇总结之后我逐渐把整个流程固化下来了。这里分享给大家你可以直接拿来用也可以根据自己的习惯调整。6.1 每周固定做一次知识地图刷新每天做的都是零散的题目每周就应该做一次汇总。我一般会在周末花一小时打开这一周做过的所有题目按知识点重新过一次看这一周在哪些方向有新增哪些方向还薄弱。这个动作有点像看地图更新。你每天刷题是在地图上画点周末汇总就是把点连成线看出这周的整体走向。如果没有这个步骤你只知道这周做了十五题但不太清楚自己的知识结构在往哪个方向变化。每次刷新时我会问自己三个问题这周新增了哪些知识点有哪些题做错了但错因相同下周的重点方向是什么回答完这三个问题下一周的刷题计划自然就出来了。6.2 建立错题必做三遍机制错题是刷题中最宝贵的资产因为它是最精准地标记你思维盲区的地方。我的机制是第一次做错隔三天重做再错隔两周再重做还错那就说明这个知识点不只是不会的问题而是理解深度不够需要专门去补这个知识点的基础而不是反复做同一道题。这个机制帮我省了很多无谓的重复。很多人在错题上反复打转同一个错误犯十次就是因为缺少升级机制——错了就再做做对了当时觉得会了过两周又忘了。而我的三遍机制每次重做之间都隔了足够长的时间能真正检验长期记忆效果。6.3 从会做题到会讲题最近一年我开始尝试一个更高阶的训练把自己做过的经典题目写成讲稿用最通俗的方式讲给别人听。这个过程远比想象中有效——你自认为懂的东西一旦试图讲清楚会发现很多地方经不起推敲。比如有一次我以为自己完全理解了最长公共子序列的动态规划解法但当我想解释清楚为什么dp[i][j]的定义能保证状态转移的正确性时发现自己其实卡了很久。这个卡住的地方正是我的理解盲区。讲完之后这道题才算真正内化了。所以我特别建议大家找一两个同样在刷题的朋友互相讲题。不用讲太复杂的题中等难度的题就够了。能把一道中等题讲得对方点头说明你理解得够深讲不明白的地方就是你该回去补课的地方。一路刷到第24篇总结最大的感受是LeetCode的意义从来不是刷过多少题而是你通过这些题构建了多强的算法思维。我做这套总结笔记本质上是想给自己留下一个可检索的大脑外挂。如果你也在刷题不妨试试这个思路——从今天开始不急着做新题先花一点时间把最近做过的题按知识点归归类也许收获比你预期的大得多。
返回列表