2026/2/23-2026/3/1 LeetCode 每日一题刷题复盘:从基本计算器到二分答案的一周
翻了翻这周的打卡日历,2026/2/23到2026/3/1这七天,题目类型分布还挺有意思的:两道栈相关、两道二分答案、一道双指针滑窗、一道贪心,外加周日那场周赛430的题。整体难度中上,没有那种签到送分题,但对“用栈处理表达式”“在值域上二分”这类高频考点的覆盖非常到位。如果你正处于刷题中期、想专攻某一类套路,这一周的题目单很值得拿出来反复嚼一嚼。
先说这周最值得关注的两个主角:一个是“基本计算器”系列,另一个是“爱吃香蕉的狒狒”。前者是栈类题目的天花板级模板,后者是二分答案入门的标杆题。把这两道吃透,很多变体题你都能一眼看穿考查点。文章后面我会把这七天的题按套路归类,逐个讲清楚我的解题思路、写出来的代码、踩过哪些坑、以及现在回头看哪种解法才是最优解。和往常一样,这周我也记录了每道题的耗时、错误次数和最终优化方案,想着分享出来,给正在按每日一题路线走的朋友们做个参考。
1. 一周题目总览与题型分布:先看整体再逐题突破
1.1 七天题目清单与难度评估
先把这周的每日一题完整列出来,包括题号、题名和我的主观难度打分(满分五颗星)。注意,LeetCode 每日一题是跟着题库轮换的,所以你在同一天看到的题可能跟我不一样,但只要是这一周窗口内的题,思路都是相通的。
| 日期 | 题号 | 题名 | 核心考点 | 难度打分 | 我的提交次数 |
|---|---|---|---|---|---|
| 2/23 | 224 | 基本计算器 | 栈、括号展开 | ★★★★ | 4 |
| 2/24 | 227 | 基本计算器 II | 栈、运算符优先级 | ★★★ | 2 |
| 2/25 | 875 | 爱吃香蕉的狒狒 | 二分答案 | ★★★ | 3 |
| 2/26 | 986 | 区间列表的交集 | 双指针 | ★★ | 1 |
| 2/27 | 1011 | 在 D 天内送达包裹的能力 | 二分答案 | ★★★ | 2 |
| 2/28 | 402 | 移掉 K 位数字 | 单调栈、贪心 | ★★★★ | 5 |
| 3/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) { Deque<Integer> 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 基本计算器 II(227)——优先级带来的一次性出栈判断
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", k=1的结果是"200",不是"0200",要注意去掉前导零。 - 如果结果是空串,要返回
"0"。
这道题我第一次提交了 5 次才过,主要死在“前导零处理”和“剩余移除次数”两个边界上。说实话,这题比基本计算器 II 更容易写错,因为它表面上是字符串操作,实际上考的是“单调栈什么时候弹出”的判断力。
最终通过的代码是:
class Solution { public String removeKdigits(String num, int k) { Deque<Character> 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(),但栈里其实没有元素。这种情况通常说明你的入栈逻辑少了“遇到(时压栈”这一环。我的建议是:写完代码后先把括号不匹配的用例跑一遍,比如"(1+2))"和"((1+2)",能快速暴露问题。
内存超限(MLE)在基本计算器系列里不常见,但在 402 题里会出现。如果你用String的不可变字符串来存储中间结果,每次deleteCharAt都是 O(n) 的复制操作,在极长输入下会非常慢。正确的做法是用Deque<Character>栈,最后一次性拼成字符串,时间复杂度才是 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 要大。
还有一个小技巧想分享:每天做完题之后,花五分钟在评论区或者自己笔记里把这道题的最优解思路写一遍,用“给别人讲题”的口吻。这样做的原因是,你在“输出”的过程中会发现自己哪些地方其实没想透。我坚持这个习惯半年了,明显感觉解题速度和学习效率都在涨。
如果你也打算开始做每日一题或者正在坚持,这周的单子完全可以作为一阶段练习。不要被“中上难度”吓住,分套路练习、逐个击破,比一上来就做随机困难题要稳妥得多。如果这篇复盘对你有一点点帮助,别吝啬点个赞。咱们下周继续记录,到时候再复盘新一周的题目玩法。