LeetCode每日一题复盘:从基本计算器到二分答案的套路总结
2026/2/23-2026/3/1 LeetCode 每日一题刷题复盘从基本计算器到二分答案的一周翻了翻这周的打卡日历2026/2/23到2026/3/1这七天题目类型分布还挺有意思的两道栈相关、两道二分答案、一道双指针滑窗、一道贪心外加周日那场周赛430的题。整体难度中上没有那种签到送分题但对“用栈处理表达式”“在值域上二分”这类高频考点的覆盖非常到位。如果你正处于刷题中期、想专攻某一类套路这一周的题目单很值得拿出来反复嚼一嚼。先说这周最值得关注的两个主角一个是“基本计算器”系列另一个是“爱吃香蕉的狒狒”。前者是栈类题目的天花板级模板后者是二分答案入门的标杆题。把这两道吃透很多变体题你都能一眼看穿考查点。文章后面我会把这七天的题按套路归类逐个讲清楚我的解题思路、写出来的代码、踩过哪些坑、以及现在回头看哪种解法才是最优解。和往常一样这周我也记录了每道题的耗时、错误次数和最终优化方案想着分享出来给正在按每日一题路线走的朋友们做个参考。1. 一周题目总览与题型分布先看整体再逐题突破1.1 七天题目清单与难度评估先把这周的每日一题完整列出来包括题号、题名和我的主观难度打分满分五颗星。注意LeetCode 每日一题是跟着题库轮换的所以你在同一天看到的题可能跟我不一样但只要是这一周窗口内的题思路都是相通的。日期题号题名核心考点难度打分我的提交次数2/23224基本计算器栈、括号展开★★★★42/24227基本计算器 II栈、运算符优先级★★★22/25875爱吃香蕉的狒狒二分答案★★★32/26986区间列表的交集双指针★★12/271011在 D 天内送达包裹的能力二分答案★★★22/28402移掉 K 位数字单调栈、贪心★★★★53/1周赛430见下方拆解综合因人而异——你能看出这几天的题其实有个隐藏的递进关系2/23 和 2/24 连续两天都是“基本计算器”一天带括号、一天不带摆明了让你把栈的边界问题彻底搞明白。而 2/25 和 2/27 两道二分答案题一道是“吃香蕉”一道是“运包裹”本质是一个模型换了层业务皮。这就是 LeetCode 的排题妙处同样一个套路连着出两遍帮你把「识别题型」的肌肉记忆建立起来。1.2 刷题节奏安排与时间管理如果时间有限我建议你的刷题顺序不要按日期来而是按套路来。正确的组合方式是第一天2/23 基本计算器 2/24 基本计算器 II一次性把“栈处理表达式”这个专题打通。第二天2/25 爱吃香蕉的狒狒 2/27 在 D 天内送达包裹的能力集中突破二分答案。第三天2/26 区间交集 2/28 移掉 K 位数字前者热身后者硬啃单调栈。周赛 430 放在周日当天按比赛节奏做。这样安排的好处是你的大脑在同一时间段只处理一种思维模型学习效率比一天一变高很多。我实测下来按套路分组刷每道题的平均理解深度明显高于零散刷题。另外提一句做每日一题时不要只盯着“今天这道”一定要回头看前一天的题。比如你 2/23 没做明白基本计算器2/24 再做基本计算器 II 时就会特别痛苦因为 II 是在 I 的基础上砍掉了括号、增加了乘除优先级属于迭代关系。如果你跳过了 I 直接做 II你会在“什么时候出栈”这个问题上卡特别久。2. 核心细节解析与实操要点基本计算器系列到底在考什么2.1 基本计算器224——括号展开与符号栈的经典配合先聊 2/23 这道 224. 基本计算器。题目给你一个字符串表达式只包含数字、加号、减号、括号和空格让你实现一个基本计算器来计算它的值。注意这题没有乘除这是关键。第一眼看上去你可能会想用两个栈一个数字栈一个操作符栈经典的双栈法。但实际上这道题真正考的是“拆括号”。拆括号的核心思路是维护一个sign变量表示当前数字的“真实正负号”再维护一个符号栈ops来存储括号外的符号状态。每遇到一个左括号就把当前的sign压入符号栈每遇到一个右括号就弹出一个符号。这个过程本质上是在做符号的“翻转”。我自己第一版代码是按双栈法写的数字栈 操作符栈写完之后长了而且 debug 了三次才过。后来看了题解才意识到在只有加减的情况下根本不需要操作符栈一个符号栈就足够了。因为加减法的本质是给每个数前面加一个符号位数字入栈的时机只在遇到运算符或者右括号时触发。这里有一个特别容易踩的坑字符串里可能有空格。很多人第一遍遍历时没过滤空格导致字符判断出错。我的做法是在循环开头直接if (c ) continue;无脑跳过干净利落。还有一个坑是“负数开头”的情况比如-2 1。这需要在遍历前把sign初始化为 1并且当第一个有效字符是-时直接将结果加到累计值上。换句话说遇到数字时不要急着压栈先判断它前面的符号是什么。我最终通过的代码如下Java 版核心是一个栈 一个累计值class Solution { public int calculate(String s) { DequeInteger ops new ArrayDeque(); int sign 1; int res 0; int i 0; int n s.length(); while (i n) { char c s.charAt(i); if (c ) { i; } else if (c ) { sign 1; i; } else if (c -) { sign -1; i; } else if (c () { ops.push(sign); i; } else if (c )) { sign ops.pop(); i; } else { int num 0; while (i n Character.isDigit(s.charAt(i))) { num num * 10 (s.charAt(i) - 0); i; } res sign * num; } } return res; } }这个写法对比双栈法的最大优势是空间复杂度 O(1)不考虑括号嵌套时代码量少一半逻辑也更不容易绕晕。我建议你把这个模板背下来基本计算器 III 甚至 IV 都能在这个骨架上扩展。2.2 基本计算器 II227——优先级带来的一次性出栈判断2/24 这道 227 就更有意思了。表达式里只有加减乘除没有括号。乍一看比 224 简单但多了优先级判断之后处理逻辑反而更绕。这题的核心思路是维护一个数字栈当遇到乘号或除号时立刻从栈顶弹出一个数字与当前数字做运算后重新压栈当遇到加号或减号时把带符号的数字压栈。最后把栈里所有数字累加。为什么要分两步因为加减是低优先级操作遇到加减符号时你并不知道后面的数字会不会被乘除“抢走”只能先入栈等最后一起算。而乘除是高优先级操作遇到乘除时后面的数字是明确的可以立即计算不需要等。我第一次写这题时犯了个错误我用一个preSign变量记录前一个运算符但初始化的时机不对导致第一个数字没有入栈。正确的做法是初始preSign 当遇到第一个数字时直接按号处理把数字压栈。这个细节很重要。另外这题的数字解析和 224 一样需要考虑多位数字。我的做法是内层while循环连续读数字直到遇到非数字字符为止。还有一个小坑表达式末尾可能没有空格也可能有空格所以循环结束后需要额外处理最后一个数字。如果你用preSign 当前数字入栈的逻辑放在循环里那最后一个数字会在循环结束时恰好被遗漏需要在循环外加一次处理这个非常容易忽略。这题的时间复杂度是 O(n)空间复杂度 O(n)栈存储属于标准的线性扫描。做这道题时你可以想一下如果加入括号代码要怎么改你会发现加括号之后你的preSign不够用了就得回到 224 那道题的符号栈方案——这就是 LeetCode 出题人想让你体会到的“从简到繁、从繁化简”。2.3 移掉 K 位数字402——单调栈的数字贪心思路如果你觉得前面两道栈已经差不多了2/28 的 402 会给你上点强度。题面是给一个非负整数字符串要求移除 k 位数字使得剩下的数字最小并且不能改变相对顺序。拿到题先别急着暴搜这题是一个典型的“贪心 单调栈”问题。核心思想是从左到右遍历数字如果当前数字比栈顶数字小说明栈顶数字“挡住了”更小的数字应该弹出栈顶并消耗一次移除机会。这个“削峰”的过程就是贪心每次都把当前能移除的最大峰值给移掉最终留下的就是最小的序列。你可以类比成“挤痘痘”把冒头的尖尖都挤掉留下来的自然是平滑的最小值。这里有几个细节我必须强调如果遍历完了还没用完 k 次移除机会直接从栈顶弹出剩余次数即可因为此时序列已经是非递减的删末尾最大。结果可能以 0 开头比如10200, k1的结果是200不是0200要注意去掉前导零。如果结果是空串要返回0。这道题我第一次提交了 5 次才过主要死在“前导零处理”和“剩余移除次数”两个边界上。说实话这题比基本计算器 II 更容易写错因为它表面上是字符串操作实际上考的是“单调栈什么时候弹出”的判断力。最终通过的代码是class Solution { public String removeKdigits(String num, int k) { DequeCharacter stack new ArrayDeque(); for (char c : num.toCharArray()) { while (!stack.isEmpty() k 0 stack.peek() c) { stack.pop(); k--; } stack.push(c); } while (k 0) { stack.pop(); k--; } StringBuilder sb new StringBuilder(); while (!stack.isEmpty()) { sb.append(stack.pollLast()); } while (sb.length() 1 sb.charAt(0) 0) { sb.deleteCharAt(0); } return sb.length() 0 ? 0 : sb.toString(); } }注意stack.pollLast()这一步因为 Deque 当栈用栈底在尾部栈顶在头部最后拼字符串时要从栈底往栈顶拼保证数字顺序正确。这个细节很多人第一次写都会错。3. 实操过程与核心环节实现二分答案的建模套路与完整代码3.1 爱吃香蕉的狒狒875——从题意到二分边界的完整推导2/25 这道 875 是二分答案中的入门神题。题面是一种诡异的设定狒狒一小时最多吃一堆香蕉如果一堆超过速度 k 根它这一小时只吃 k 根吃不完的下小时继续如果一堆不够 k 根它吃完这堆后不会去动下一堆宁可发呆。问题是给定一个总时间上限 H问最小速度 k 是多少。我第一次看到这个题的时候想都不想就暴力枚举 k从 1 试到最大堆根数。这样做当然也能过一部分用例但 LeetCode 的测试数据不会让你这么舒服最大堆可能到 10^9你枚举到天荒地老。正确做法是二分答案。为什么这题能二分关键在于“满足单调性”速度 k 越大吃完所有香蕉所需的总时间越小或不变。换句话说函数f(k) 总耗时 是单调递减的你需要在值域上找满足f(k) H的最小 k。这就是标准的最小化最大值问题。对于每一堆p吃掉它需要的时间是ceil(p / k)。注意不能直接p / k取整那是向下取整会导致时间算少。正确写法是(p k - 1) / k这是整数向上取整的标准写法不需要浮点数运算。二分边界怎么定左边界是 1不可能比 0 还小0 没法吃香蕉右边界是最大堆的香蕉数为什么要用这个因为当 k 等于最大堆根数时每堆都可以在一小时内吃完这已经是最优情况的上限再大也没有意义了。在这个基础上二分的判断条件就是总耗时 H。满足时说明速度可以再慢一点把右边界往左收不满足说明速度太慢了得把左边界往右推。我写出的最终代码是class Solution { public int minEatingSpeed(int[] piles, int h) { int left 1; int right 0; for (int p : piles) { right Math.max(right, p); } while (left right) { int mid left (right - left) / 2; if (canFinish(piles, h, mid)) { right mid; } else { left mid 1; } } return left; } private boolean canFinish(int[] piles, int h, int speed) { int time 0; for (int p : piles) { time (p speed - 1) / speed; if (time h) return false; } return time h; } }这里有一个性能优化点canFinish函数里我加了if (time h) return false;意思是提前剪枝——如果已经超时了直接终止循环不用再算了。这个优化在极端情况下能省不少时间建议你写二分答案题的时候都考虑一下提前退出的条件。3.2 在 D 天内送达包裹的能力1011——同一模型的第二道验证2/27 的 1011 和 875 是同一个模子刻出来的。题面是按顺序把包裹装上船船每天最多载重 x你必须在 D 天内把所有包裹运完问船的最小载重量是多少。你甚至可以把“包裹”类比成“香蕉堆”“船的最小载重量”类比成“狒狒的最小进食速度”“D 天”就是“H 小时”。这题的二分会更隐蔽一点因为它的f(x) 按载重 x 运输所需的天数同样是单调递减的。左边界是所有包裹中的最大重量因为船单日载重至少得能装下最重的一件否则永远运不完右边界是所有包裹重量之和一天全运完。判断函数怎么写用一个days变量计数用一个current变量代表当天已累计载重。遍历每个包裹如果current weight x说明今天装不下了天数加一然后current weight否则current weight。循环结束后如果current 0天数还要加一因为最后一天可能有残余。这里有个容易错的地方如果你把days初始化为 0但每次都把最终天数 days 1计算就会在边界判断上出错。我的做法是days 1初始化然后循环内遇到超重才days这样语义更清晰。这道题作为 875 的对照题非常值得连着做一遍。两道题都吃透后你就能总结出二分答案题的标准套路第一步确定一个带有单调性的函数第二步确定二分边界第三步写判断函数。三步走清楚了任何变体都逃不出这个框架。3.3 区间列表的交集986——双指针在有序区间上的落点2/26 的 986 算是这周的一股清流不需要栈也不需要二分就是单纯的双指针。题目给了两个按端点升序排序的区间列表要你求它们的交集区间。这题的思路很直观用两个指针 i 和 j 分别指向两个列表每次取两个区间的交集然后移动“右端点更靠左”的那个指针。为什么移动右端点更靠左的指针因为下一个区间要跟“右端点靠右”的区间继续找交集如果移动右端点靠右的区间指针你可能会漏掉一些还没用到的区间。这个选择逻辑是双指针题的核心谁更可能“用完”谁就先走。计算交集的公式也很固定int left Math.max(first.start, second.start); int right Math.min(first.end, second.end); if (left right) { res.add(new int[]{left, right}); }这里有三个注意点一是left right才有效等于也算相交点交集二是一定要判断越界三是题目要求返回若干区间不是要你合并区间所以不需要处理相邻区间合并的情况。这道题我一遍就 AC 了不是因为我聪明而是因为我之前做过 56 题“合并区间”和 57 题“插入区间”。区间类题目的套路高度相似你只要把“排序、比较端点、移动指针”这三板斧练熟基本能解决 90% 的区间题。4. 常见问题与排查技巧实录这一周我踩过的坑和填坑方法4.1 基本计算器系列符号栈何时入栈、何时弹出如果你在 224 题上卡住大概率是符号栈的使用时机没搞明白。我把这个坑展开细说遇到或-时仅设置当前符号变量sign不操作栈。遇到(时将当前sign压栈。核心原因括号内部分的最终符号取决于括号外符号与该括号前符号的“结合”。例如5 - (2 3)遇到(时当前符号是-所以把-压栈括号内的实际代表-。遇到)时从栈中弹出符号。原因括号结束括号内符号的“外部影响”已结束恢复外层符号状态。这个“符号恢复”的概念很多人第一遍是理解不了的我建议你拿一道带两个负号的例子手动模拟一遍比如1 - (-2)你会发现符号栈那一层的压入和弹出正好对应括号嵌套的开始与结束。这个机制理解之后224/227/772基本计算器 III三题都能用同一套思维解决了。4.2 二分答案为什么右边界是max而不是随便一个大数很多二分答案初学者会困惑右边界怎么定。以 875 题为例有人直接把右边界设成Integer.MAX_VALUE或者干脆设成所有香蕉堆的总和这两者其实都不对。设成Integer.MAX_VALUE的问题是二分次数变多可能多出十几次循环虽然不至于超时但不优雅。设成总和的问题是逻辑上不够“紧”因为当 k 大于最大堆根数时吃香蕉的速度已经达到极限了再往上提不会减少总耗时属于“无效区间”。正确做法是让右边界刚好卡在“使问题达到最优值的最小可能值”上。875 题的答案是“最大堆的香蕉数”1011 题则是“所有包裹重量和”。你可以这样记忆对于“速率/容量”类问题右边界的物理意义是“单次操作最多需要处理的量”。我还想提醒一点二分答案题里判断函数的写法决定了你能不能 AC。如果你把ceil写成了整数除法875 题的错误率会非常高因为一堆 5 根、速度 2你算成了 2 小时实际需要 3 小时。每次写完判断函数后先用三个小样例手算一遍再跑测试用例这是我在刷二分题时养成的习惯。4.3 栈类题目中超时与内存错误的常见信号栈类题目常见的错误有两种一种是EmptyStackException另一种是内存超限。EmptyStackException往往是你在遇到)时直接pop()但栈里其实没有元素。这种情况通常说明你的入栈逻辑少了“遇到(时压栈”这一环。我的建议是写完代码后先把括号不匹配的用例跑一遍比如(12))和((12)能快速暴露问题。内存超限MLE在基本计算器系列里不常见但在 402 题里会出现。如果你用String的不可变字符串来存储中间结果每次deleteCharAt都是 O(n) 的复制操作在极长输入下会非常慢。正确的做法是用DequeCharacter栈最后一次性拼成字符串时间复杂度才是 O(n)。另外Deque接口的push/pop/peek方法是属于栈语义的但它是从队首插入和取出所以如果你想遍历栈内元素需要用Iterator或者pollLast。我在 402 题里就用了pollLast来按正确顺序取出元素这个细节值得你记住。4.4 周赛 430 速览日常刷题如何迁移到竞赛场景3/1 的周赛 430 我参加了一下整体难度中规中矩考点分布集中在数组模拟、贪心、二分搜索和树形结构这几类。这周每日一题练的“单调栈”和“二分答案”在周赛里都有变体出现比如有一道题是让你在满足某种条件的数组中找最小可行解原理跟 875 高度重合只是判断函数复杂了一些。关于周赛我的建议是别把它当测试当成“限时练习”就好。每周日上午 10:30 雷打不动参加一场连续 8 周之后你就能明显感到自己的代码手速和边界判断力提升了。不要因为某场打崩了就气馁周赛的价值在于“暴露问题”不排名次。5. 这一周的可复用方法论三类套路三级跳5.1 套路一栈的适用场景识别通过这一周的题你可以看出栈类题目通常有几个信号表达式中出现括号、需要维护一个“当前状态”且在遇到某些符号时回退、题目要求“删除/移除若干元素后达到最优”。不管是基本计算器还是移掉 K 位数字栈的核心都是一种“延迟决策”的工具——它让你暂时保存一些元素等后续信息出现再决定去留。所以下次你遇到“从左到右遍历但某些元素要等后续条件满足后才处理”的场景第一反应就应该想到栈。不要一上来就想着用字符串操作硬解那样时间复杂度通常会退化到 O(n^2)。5.2 套路二二分的本质是值域搜索875 和 1011 两道题的本质是“在答案的值域上做二分”。很多题看起来不是“查找”比如“求最小速度”“求最小载重”“求最小等待时间”但实际上它们的共同点是存在一个衡量方案好坏的单变量函数且该函数在值域上单调。只要你能找到这个单变量和单调函数就可以无脑二分。我做二分答案题的习惯是先把暴力枚举的代码写出来不用跑只是理逻辑然后找出那个“答案变量”再写canFinish判断函数。这能帮你快速定位二分的左右边界和判断逻辑减少思路混乱导致的无谓提交。5.3 套路三双指针的关键是“移动哪一侧”986 题的双指针并不难但很多人容易在“移动哪个指针”上犯错。我的经验法则是哪一边的右端点更小就移动哪一边。原因是右端点更小的区间已经没有潜力跟后面的区间产生交集了它的“生命周期”已经结束留着也没用。这个思想在很多区间题、合并题里是通用的只要你理解了“生命周期结束就移除”双指针题的代码基本不会写错。另外双指针题里一个重要的提速细节是每次循环检查两个指针是否越界时只检查那个被移动的指针就够了因为另一个指针在上一步已经保证有效。如果你每次都检查两个指针代码会显得冗余且容易出错。6. 最后的实操心得关于这周刷题我最后想说的这周七天刷下来我最明显的感受是LeetCode 的每日一题排期越来越喜欢按“套路连击”来设计了——基本计算器两道连着出二分答案两道连着出摆明了就是让你在短时间内把一个专题吃透。如果你只是每天打卡完就关掉第二天做类似套路的题还是会卡壳但如果能像我上面建议的那样按专题把相邻几天的题连起来做再把错题统一复盘一遍效果会好很多。我个人在刷题中反复踩坑后的一个体会不要追求提交次数少而是追求每次提交之后能准确说出错在哪。我第一次做基本计算器就提交了四次但我把每一次失败的原因都记录下来了——第一次是空格没处理第二次是括号符号压栈时机不对第三次是符号变量没及时重置第四次才通过。回头去看这四次提交的价值远比一次 AC 要大。还有一个小技巧想分享每天做完题之后花五分钟在评论区或者自己笔记里把这道题的最优解思路写一遍用“给别人讲题”的口吻。这样做的原因是你在“输出”的过程中会发现自己哪些地方其实没想透。我坚持这个习惯半年了明显感觉解题速度和学习效率都在涨。如果你也打算开始做每日一题或者正在坚持这周的单子完全可以作为一阶段练习。不要被“中上难度”吓住分套路练习、逐个击破比一上来就做随机困难题要稳妥得多。如果这篇复盘对你有一点点帮助别吝啬点个赞。咱们下周继续记录到时候再复盘新一周的题目玩法。
上一篇/下一篇内容由系统自动关联
返回资讯列表 →