ARTICLE DETAIL

资讯详情

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

LeetCode热题100复刷:二分答案框架与073爱吃香蕉的狒狒深度拆解

LeetCode热题100复刷:二分答案框架与073爱吃香蕉的狒狒深度拆解 最近LeetCode热题100又开始第三轮换血我趁着周末把新上榜的题目和之前遗留的难点统一过了一遍过程中发现热题100五这批题目很有意思它们不再单纯堆砌数据结构而是大量把“二分答案”“前缀和差分”“区间调度”这类思维模型揉进看似简单的背景里。今天这篇就围绕我反复刷的几道代表题展开重点拆一下073爱吃香蕉的狒狒顺带聊聊这一阶段刷题的整体思路和周赛430带来的启发。如果你是准备校招、跳槽刷题或者刚入坑想从热题里建立解题框架这篇的内容应该能帮你省下不少自己摸索的时间。1. 热题100第五期复刷的选题思路1.1 为什么我停掉题海战术回到热题100我大概从三个月前开始停止盲目按题号刷题。原因很简单刷了三百多道之后发现真正在面试里反复出现的永远是那几类固定套路。LeetCode热题100之所以被推到这么高的位置是因为它筛选出来的题目基本覆盖了面试中最高频的考点双指针、滑动窗口、二分查找、二叉树遍历、动态规划基础、图的最短路径再加上一些经典的贪心和回溯。第五批热题里面我数了一下大概有19道是之前没出现过的其余的都是在老题基础上加了限制条件或改了数据范围。比如有些题从数组改为链表实现有些题把静态查询改成动态更新。这种变化本质上是在考察同一个模型在不同容器下的适配能力。所以我在复刷时给自己定的规则是每道题做完之后不急着看题解区先自己尝试改一个条件或者换一种输入结构看原来的解法还成不成立。就像073爱吃香蕉的狒狒这道题表面上是“吃香蕉”的生活化场景实际上是一道非常标准的二分答案模板题。第一眼看到“每小时最多吃多少根才能在H小时内吃完”这种问法就应该立刻反应过来这是在有限定条件下求最小可行值典型的二分查找左边界问题。这类题在热题100里不止一道和它同型的还有造桥、运货、切木头等变体只要框架搭对了后面遇到新题就只是换一层皮的事。1.2 热题100五这批题目的考点分布分析我按自己的刷题记录统计了一下这批题目大概可以分成四类第一类是模拟题主要考代码实现力和边界条件处理占比约18%。这种题难度一般但极容易在细节上挂掉。第二类是经典算法模型题二分答案、拓扑排序、并查集等占比约34%。这是热题100真正的精华区也是面试官最喜欢深挖的部分。第三类是DP优化题从朴素DP到滚动数组、状态压缩占比约12%。这类题要求你不仅能写出转移方程还要能解释空间复杂度的优化逻辑。第四类是“换皮变形题”占比约36%。这部分的题目背景各异但核心解法都来自前两类。这个分布说明一个问题现在的大厂面试已经从“背模板”转向“验模型”。你就算把一道题的题解背得再熟换一个背景模拟题可能还是一脸懵。所以我特别推荐把热题100当思维模型库来用每道题做完之后都问自己一句这题最核心的模型是什么它还能套到哪些场景里2. 073爱吃香蕉的狒狒一道被低估的二分模板题2.1 题目真正在考什么先看题目陈述一只狒狒面前有n堆香蕉每堆有若干根狒狒每小时可以选择一堆吃掉其中k根如果这堆不足k根就全部吃掉并且这一小时内不能再吃其他堆。给一个总时限H问最小的k是多少能让狒狒在H小时内吃完所有香蕉。我第一次做这道题时第一反应是模拟贪心从k1开始递增逐步判断能不能在H小时内完成。写完代码一跑数据量大的用例直接超时。后来才意识到这里面藏着一个非常典型的二分查找模型。为什么能二分因为“在H小时内能否吃完”这个判定函数f(k)具有单调性k越大吃得越快越容易在H小时内完成k越小越容易超时。单调性是二分的前提这比题目本身更重要。顺便说一句网上很多人管这道题叫“哑巴吃香蕉”或者“烧烤狒狒”就是因为koko这个词被音译成了各种搞笑的叫法。但名字是次要的它的核心价值在于帮我们建立一个思维反射看到“求满足条件的最小值”就问自己能不能二分。2.2 判定函数的实现细节这道题的核心在于check函数怎么写。对于给定的速度k我们要计算吃掉所有香蕉需要的小时数然后和H比较。这里的计算公式是堆数累加对每一堆piles[i]吃掉它所需小时数是 ceil(piles[i] / k)而不是简单的整除。这里有个陷阱很多人会写成 piles[i] // k 忽略了余数。举个实际场景如果一堆有10根香蕉速度是3根/小时那么需要4小时而不是3小时。用代码表示就是 (piles[i] k - 1) // k这是做上取整的标准写法。再注意一个细节一次性遍历所有堆累加总时长时需要考虑这个总时长会不会超过H。如果中间累加已经超过H可以提前break返回False没必要继续算完。这个“提前终止”的优化在数据量大时能节省不少时间是check函数里非常值得加的一个小优化点。2.3 边界条件和二分框架怎么定二分查找的左右边界是整个题目的另一大重点。左边界最小取多少理论上k最小可以是1也就是每小时只吃1根不能再小了。右边界呢最慢的策略是每小时吃掉一堆香蕉不管这堆有多少根反正这一小时全吃完所以最大需要的速度就是max(piles)也就是最大堆的根数。为什么右边界是max(piles)而不是sum(piles)因为在当前约束下如果k等于max(piles)意味着每一堆都能在一小时内吃完此时总耗时就是堆数n。题目保证H n否则无解比如H小于堆数时连“每堆吃一小时”都不够所以任何大于max(piles)的k都不会让结果更快也就没有意义了。二分框架我用的是左闭右开的经典写法def minEatingSpeed(piles, H): def check(k): hours 0 for p in piles: hours (p k - 1) // k if hours H: return False return hours H left, right 1, max(piles) 1 while left right: mid (left right) // 2 if check(mid): right mid else: left mid 1 return left这里右边界我用的是max(piles) 1配合闭区间语义可以避免一些死循环问题。如果你习惯用闭区间加等号判断也完全没问题关键是一旦想清楚自己的开闭约定就要保持一致否则在边界上容易出错。2.4 时间复杂度推算这道题的二分次数是log(max(piles))每次check需要遍历一遍piles数组所以总时间复杂度是O(n log max(p))其中n是堆的数量。空间复杂度是O(1)因为除输入数组外没有使用额外存储。我测了几个典型数据当piles长度是10^4、max(piles)是10^9时二分只需要约30轮每轮遍历一万个元素总计算量在30万这个量级完全没有任何压力。这也是为什么二分答案能在热题里吃得开——它的复杂度从朴素的O(max(p))直接降到了对数级这是质变而不是量变。3. 从热题100到周赛430把模型迁移到新题3.1 周赛430给我的一些提醒最近几周的周赛特别是430这场我在做题时明显感到一种趋势题目经常把二分答案嵌套在另一个数据结构里。比如先对答案做二分然后在判定时借助前缀和、差分数组或者线段树来加速统计。这种复合型的考法如果再按早期的“死背模板”思路应对十有八九会翻车。430这场里有一道题是给一个数组要求通过回溯的方式切分若干段并且在每个分段里做某种满足特定条件的统计。我当时第一反应是DFS加记忆化但现场写着写着发现状态定义不是很好搞后来跳出来想这其实可以看作“DFS框架 预处理加速”的组合题。这和热题100里的很多题目一样框架本身不复杂复杂的是如何在框架的分支里高效地完成局部判断。所以我在做热题100时反复强调“回归模型”原因就在这——你永远不知道下一道题会把模型包装成什么样子但模型的底层逻辑是有限的把它们吃透就能兵来将挡。3.2 一个可以立刻套用的迁移案例再拿073爱吃香蕉的狒狒的二分框架来举例它可以直接迁移到这些场景给定每个包裹的重量数组要求在H天内运完所有包裹求最小的单日运载能力。判定函数就是“按顺序累加包裹重量超过k就开新的一天”。给定一根木头的长度n以及需要切成的段数m求切出的最大可能长度。判定函数就是“以长度k来切能切出多少段”。给定工厂的生产速率和订单量在给定周期内求最小产能。共同点是什么都是“求某个参数的最小可行值”而且这个参数的值越大越容易满足约束越小越难满足。这个单调特征一旦识别出来二分框架直接套用即可。强烈建议你把073的check函数和左右边界推导过程自己独立写一遍再换个场景写一遍比抄十道题记住十个模板有用得多。3.3 迁移时容易踩的两个坑第一个坑判定函数算出来的值和约束条件单位不一致。比如运货问题073里check返回的是小时数运货问题里check返回的是天数有些人会把H的单位搞混导致边界判断出错。其实只要搞清楚“变量代表什么单位”这个问题就不存在了。第二个坑二分左右边界凭感觉瞎选。我看到不少人把左边界设为0这在小部分二分里可行但在073这类题里会导致除零错误。另一些人把右边界设为sum(piles)这在物理意义上是“每秒吃一根直到所有吃完”——其实这个上界确实能让答案正确但循环轮数会比max(piles)多出好几倍没必要。记住上界的选择要尽量紧贴着“可能的最优解”不要偷懒往大了塞。4. 热题100复刷避坑指南与调试心得4.1 二分查找里最常见的死循环问题我自己的调试经验里二分查找的死循环通常出现在mid计算和更新条件搭配不合理时。比如经典的left mid 1和right mid搭配时如果用的是(left right) // 2通常没问题但如果你写成了left mid也用了向下取整那么当区间长度只剩2且两个值都满足条件时left永远卡在同一个mid上无限循环。我规避死循环的方法是记住一个口诀向下取整配合left mid 1向上取整配合right mid - 1。073这题里我用的是左闭右开区间所以更新规则是right mid不需要减1left mid 1两者天然配套。你在其他题里如果用一个闭区间写法记得把mid上取整公式(mid (left right 1) // 2)也用上保证不要死循环。4.2 上取整运算的优先级坑再看个很小的代码细节hours (p k - 1) // k。我见过有人写成 hours p // k (p % k 0)这个其实也对但布尔值在Python里会隐式转成整数可读性差一些。更危险的写法是 hours (p // k) 1这个在p恰好被k整除时会多算一个小时——如果你用这种写法的测试用例刚好吃完结果就会差1。这类问题在自测的时候最阴险。因为样例数据往往设计得刚好能整除测出来正确一到提交就WA。我建议写check函数时主动构造几个不能整除的数据来验证比如一堆有5根香蕉、速度是2根/小时期望值是3小时而不是2.5或3.5。4.3 常见问题速查表症状可能原因排查思路提交超时check函数没有提前终止或上界过大导致二分轮数过多确认是否在累计超过H时就break确认右边界取的是max(piles)而非sum(piles)结果比答案大1mid计算和区间更新不匹配查是不是用了向上取整但配合了right mid - 1或者在整除时多算了1小时死循环mid更新方式与开闭区间不配套检查left mid时是否配合了(mid (left right 1) // 2)边界用例报错左边界设成了0确认left从1出发或在while前先排除特殊情况多语言结果不一致除法和取整语义不同Python的//是向下取整C/Java的整数除法是截断取整二者在负数场景有差异好在piles和k都是正数问题不大4.4 调试二分答案题的独门流程最后分享我的完整调试流程。第一步写check函数之前先明确它的返回值语义和单调方向。第二步用一个极小的输入比如三堆香蕉[3, 5, 2]和H6在纸面上推导一遍得到期望答案后再跑代码。第三步构造一个刚好卡在边界的用例例如[1, 1, 1, 1]和H4这时答案应该是1用来验证最小左边界是否正确。第四步构造一个H刚好等于数组长度的用例确保右边界逻辑不越界。这四步走完基本可以覆盖热题100里二分题的绝大多数坑点。如果你觉得自己的二分一直写不稳建议就用这四步把073反复练到闭眼能写后面所有二分答案题都能信手拈来。5. 一些想分享的实战体会最近热题100这批题刷下来我最大的感受是面试考的不是你会不会某一道题而是你面对全新问题时能否快速定位到已有的思维框架。073爱吃香蕉的狒狒的二分框架之所以值得反复练习不只是因为它本身常考而是它是整个“二分答案”范式的标准入口。我目前刷到不少公司的笔试题基本都能从热题100里找到影子但几乎没有一道是原题照搬全是套了一层新的叙述。我自己踩过最深的坑就是早期追求AC数量一天刷十五道简单题自我感觉良好结果一周后回想能复现的不到五道。后来我改成一天只精做两三道热题里的中难题每道都推敲边界和复杂度反而在周赛430的现场表现好了不少。刷题的数量只是流水能不能把模型沉淀下来才是真正的复利。另外我建议把每道热题的解题思路用自己的话写出来哪怕就是简单几句。写过和想过完全是两个层次。比如073这道题如果你能在一周之后不看任何提示的情况下把“单调性为什么成立、左右边界为什么取1和max(piles)加1、check函数为什么用上取整”这三个问题都讲清楚那这道题才是真正消化了。如果你正在准备面试热题100慢慢啃不要慌着跳题。把每道题的核心模型拆出来做横向对比你会发现这道题和那道题之间有惊人的相似之处。我就是这样一路走过来的也希望这篇文章能帮你在刷题路上少走点弯路。
返回列表