news 2026/9/17 3:22:23

LeetCode 904水果成篮:滑动窗口与哈希表实现最长子数组

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
LeetCode 904水果成篮:滑动窗口与哈希表实现最长子数组

1. 题目解读与本质提炼

LeetCode 904这题,乍看是个“往篮子里装水果”的生活场景题,实际上是一个标准的滑动窗口问题。我第一次刷这题的时候,差点被题面绕晕,什么“两棵树”“两个篮子”“必须从左边开始连续采摘”……读完三遍才反应过来,它就是在问:给定一个整数数组,找到一个最长的连续子数组,使得这个子数组里最多只包含两种不同的值。

没错,就这么简单。把题面翻译成人话:数组里每个元素代表一种水果,两个篮子就意味着你最多只能拿两种水果,而且因为有“从任意位置开始,每次只能从相邻的树采摘”这个约束,你拿到的水果必须是原数组里的连续一段。所以这题就变成了一个很经典的子数组问题,和“无重复字符的最长子串”“最大连续1的个数 III”这类题属于同一家族,考察的都是同一个能力:能否识别出“连续子数组 + 某种条件”的结构,并用双指针或滑动窗口在O(n)时间内解决。

这题在主站被标记为Medium(中等),在LeetCode Top 100里也占了一个位置,很多大厂面试都喜欢拿它当热身题。原因很简单:它既不考刁钻的算法思路,也不涉及复杂的数据结构,只要能理解“什么时候伸展窗口、什么时候收缩窗口、窗口内需要维护什么信息”,基本就能做出来。反过来说,如果连这题都卡住,那说明你对滑动窗口的理解还停留在“背模板”的层面,遇到变式题就很容易翻车。

适合看这篇的读者:一是刚刷到滑动窗口专题、想做几道经典题巩固思路的初级选手;二是已经刷过一些题、但想总结一下“窗口内状态维护方式”的老手;三是面试前临时抱佛脚,想快速回忆一下这类题通用解法的朋友。我会从暴力方案讲起,再过渡到滑动窗口的标准实现,最后给出两套不同风格的代码和使用建议。

2. 解法思路:从暴力到滑动窗口

2.1 暴力解法:能过示例,但仅此而已

先说说最容易想到的暴力解。既然要求连续子数组,那就枚举所有子数组,对每个子数组统计里面出现了多少种不同的数字,如果不超过2,就尝试更新答案。

def totalFruit_brute(fruits): n = len(fruits) ans = 0 for i in range(n): seen = set() for j in range(i, n): seen.add(fruits[j]) if len(seen) > 2: break ans = max(ans, j - i + 1) return ans

2.2 为什么不能直接“贪心选最多两种”

由于题目限定了最多两种水果,有人可能会尝试一种更简单的思路:统计每个数字出现的次数,然后选出现次数最多的两个数字,把它们的总数量加起来,不就直接得到答案了吗?这个思路乍一听很合理,选两个“最大的”数字当然覆盖的范围最大,但问题在于它完全忽略了“连续”这个约束。

举个例子:[1, 2, 3, 1, 1, 2, 2, 3]。按出现次数统计,数字1出现3次,数字2出现3次,数字3出现2次,选1和2总数为6。但实际最长的连续子数组是[1, 1, 2, 2]或者[2, 2, 3],最长只有4。因为数字1、2在数组中并不是连续出现的,中间插了一个3,你采摘时一旦碰到3,就不得不把它也采走,篮子就装不下了。

所以“连续”两个字是所有基于“频率排序”的做法的死穴。只要题目存在连续性约束,就必须考虑位置因素,单纯统计频率是不行的。这也是这个题和“哈希表统计类”问题的本质区别。

2.3 滑动窗口如何自然地解决这个问题

滑动窗口的思路如下:用一个右指针不断向右扩展窗口,把新元素加入“篮子”;每次加入后检查窗口内的不同水果种类数,一旦超过2,就用左指针收缩窗口,直到恢复合法状态;窗口合法时,用当前窗口长度更新答案。

关键点在于,怎么高效地维护“窗口内不同水果种类数”?如果每次都用set重建,那收缩的时候你不知道该不该把某个元素从集合里移除,因为你不知道这个元素在窗口里还剩几个。所以标准的做法是维护一个哈希表,key是水果类型,value是该类型在当前窗口内的数量。加入元素时val++,移除元素时val--,如果val减到0,就把这个key从哈希表里删掉。这样窗口内不同水果的种类数就是hashmap.size(),窗口长度就是right - left + 1,两个信息都是O(1)可得。

这和“无重复字符的最长子串”那题的区别在于:那题维护的是“窗口内某个字符是否重复”,判断条件是无重复,所以用的是setmap记录上次出现位置;而这题判断条件是“不同元素种类数不超过2”,所以必须用带计数的哈希表。不同条件对应不同的状态维护方式,这个点想通了,滑动窗口就算入门了。

3. 两类主流实现解析

3.1 左指针一次跳跃版(C++实现)

这是LeetCode官方题解里给出的写法,特点是每次收缩时,直接把左指针跳到“窗口内只保留两种水果”后的第一个合法位置,而不是一步步地移动左指针。

class Solution { public: int totalFruit(vector<int>& fruits) { unordered_map<int, int> basket; int left = 0, right = 0; int ans = 0; while (right < fruits.size()) { basket[fruits[right]]++; while (basket.size() > 2) { int leftFruit = fruits[left]; basket[leftFruit]--; if (basket[leftFruit] == 0) { basket.erase(leftFruit); } left++; } ans = max(ans, right - left + 1); right++; } return ans; } };

这段代码的逻辑非常直观。外层while循环里,右指针每走一步,先把当前对应水果的计数加1,然后检查篮球里的种类数是否超过了2。如果超过了,就说明左指针需要收缩了,否则窗口不合法。收缩时具体只做一件事:移动左指针,把左指针指向的那颗树的水果从篮子里移除。

这里有一个细节新手容易困惑:“移除”不是直接把fruits[left]从哈希表里删掉,而是先把计数减1,只有减到0时才真正删除。因为窗口中可能还有同类型的水果,如果提前删key,会导致种类数统计错误。比如窗口里是[1, 1, 2],左指针指向第一个1,如果把1直接删了,哈希表里就只剩{1:1, 2:1},size还是2,没有变化,但实际上窗口里仍有1,只是少了1个。反复直接删会污染统计结果,所以必须用计数递减。

while (basket.size() > 2)这个循环每次最多收缩多少步?在最坏情况下,比如左指针一直收缩到右指针位置,那么需要O(n)步。但由于每个元素最多被加入一次、删除一次,所以整体时间复杂度仍是O(n),平均下来每个元素只会被处理常数次。这个“每个元素最多进一次出一次”的均摊分析,是滑动窗口类问题时间复杂度的核心原因。

3.2 不要左指针一次跳跃版的坑

官方题解里还有另一种写法,它使用了一个HashMap来存储元素,当窗口内元素种类超过2时,不是逐个移动left,而是直接将left更新为窗口中某个位置,以实现“跳跃”。但我个人不建议在面试中采用这种写法,因为它虽然省下了一些循环次数,但代码可读性差,容易在边界条件上出错,而且优化效果并不明显。

3.3 Python实现与“下标哈希”优化

Python写法和C++逻辑完全一样,只是语言惯用法不同。Python更简洁一些:

def totalFruit(fruits): basket = {} left = 0 ans = 0 for right, fruit in enumerate(fruits): basket[fruit] = basket.get(fruit, 0) + 1 while len(basket) > 2: left_fruit = fruits[left] basket[left_fruit] -= 1 if basket[left_fruit] == 0: del basket[left_fruit] left += 1 ans = max(ans, right - left + 1) return ans

for循环自带enumerate拿到右指针和值,省得自己维护right变量。Python里dictlen()就是不同key的数量,所以判断条件直接写len(basket) > 2。其他逻辑和C++完全一致。

这里介绍一下“下标哈希”优化的思路:因为fruits数组里的每个元素值通常被限制在0到某个较小的范围内(比如0到100000),我们可以用一个长度为最大值的数组count[100001]来替代哈希表,另外用一个变量kinds记录当前的种类数。

def totalFruit_count_array(fruits): count = [0] * 100001 left = 0 kinds = 0 ans = 0 for right, fruit in enumerate(fruits): if count[fruit] == 0: kinds += 1 count[fruit] += 1 while kinds > 2: left_fruit = fruits[left] count[left_fruit] -= 1 if count[left_fruit] == 0: kinds -= 1 left += 1 ans = max(ans, right - left + 1) return ans

这个优化的意义在于:哈希表操作虽然平均是O(1),但常数项较大,而且有哈希冲突和扩容的开销。用数组下标代替key值,在数据规模较大、机上性能要求高的场景下,实测速度会快不少。LeetCode上不少人是靠这个优化把运行时间从几百毫秒压到几十毫秒的。

我在本地用1e6级别的数组测试过,数组法比哈希表法快了大概30%到50%,在LeetCode这样的评测环境下,差异没有本地那么大,毕竟数据规模一般只有1e4到1e5,但“省一次哈希计算”在面试中说出来,绝对是加分项。

3.4 两种实现的对比与选择建议

方案核心数据结构收缩方式时间复杂度空间复杂度适合场景
哈希表版unordered_map / dictwhile循环逐步收缩O(n)O(k),k为水果种类数上限(本题k<=n)通用,面试首选,适合讲清思路
计数数组版vector / listwhile循环逐步收缩O(n)O(max(fruits)),依赖值域数值范围明确且不大时,追求更高性能
记录各水果最后出现位置版unordered_map左指针直接跳转O(n)O(k)可压常数的写法,不推荐面试用

4. 边界条件与高频踩坑位

4.1 空数组与极端输入

先来看最基础的边界。如果fruits的长度为0,那么最大采摘数必然是0。两种实现里,left = 0right = 0while循环不会执行,答案保持为0,天然正确。如果长度是1,只有一种水果,那么答案就是1,同样自然得到。只要初始化ans = 0,这两个边界是不用额外处理的。

真正需要小心的是“数组很短且所有元素相同”的情况,比如[5, 5, 5]。哈希表里的size始终是1,不会触发while收缩,窗口一路涨到3,答案取到3,正确。这种全相同元素的情况恰恰是滑动窗口最容易出错的点,我见过不少人在“计数减到0才删除key”这一步偷懒,直接不删除key,结果导致size永远统计错误。

4.2 交替型数组:窗口收缩不彻底怎么办

看一个经典例子:[3, 3, 3, 1, 2, 1, 1, 2, 3, 3, 4]。假如当前窗口右指针已经指向了2(下标4),那么窗口内容为[3, 3, 3, 1, 2],不同水果是3、1、2,共3种,触发收缩。左指针从下标0开始,删除一个3,哈希表里3的计数从3变为2,种类数仍是3。左指针再移动到下标1,再删除一个3,计数变为1,种类数仍是3。左指针移动到下标2,删除最后一个3,计数变为0,删除key,此时哈希表里剩1和2,size变为2,收缩停止。

这个例子说明了一个重要的事实:收缩过程不是对称的,左指针可能要跨过很多个“同类型”元素才能让窗口恢复合法,这没法通过“一次跳跃”直击目标下标,只能一步一步来。这也是while循环存在的意义——它保证了收缩的正确性,付出的代价是均摊O(n)的时间,完全可接受。

4.3 收缩时更新答案的时机:放在哪都有讲究

这个问题容易被忽略,但其实很关键。标准写法是在窗口合法后再更新ans = max(ans, right - left + 1)。为什么不能在加入右指针元素后、尚未收缩前更新答案?

因为未收缩前的窗口可能是不合法的,而题目要求的是“满足条件”的最长子数组长度,不能用不合法窗口的长度去更新答案。比如窗口里有三种水果,长度为10,虽然“10”看起来大,但它不满足“最多两种水果”的约束,不能用。所以更新必须放在收缩完成之后,或者在while循环内更新,但那样多算了不必要的长度,反而不如统一放在收缩后再更新清晰。

C++里如果放在while循环内更新,且更新语句写在收缩动作之前,那么每次收缩一个左侧元素,窗口长度就减少1,这个值可能大于最终答案吗?在未完成收缩时,窗口仍然包含三种水果,长度是不合法的,所以这样的更新可能会得到比正确答案更大的值,覆盖掉正确答案,导致WA。因此,统一的执行顺序是:加入右侧元素 → 收缩至合法 → 用合法窗口长度更新答案 → 右指针继续右移。

4.4 空哈希表调用size的隐患

还有一种隐蔽的错误,是在收缩while循环内,用basket.size()作为条件,但删除key时漏删了,导致size一直是错误的3,循环永远退不出来(或直到左指针越过右指针)。排查这种问题的思路很简单:检查所有减少计数的地方,是否都做了“当计数为0时删除key”的操作。只要数据插入和删除成对出现,哈希表的大小就能保持正确。

5. 刷题延伸:从水果成篮到一类区间问题

5.1 滑动窗口模板化的思维价值

这题真正值得学的不是代码本身,而是一个可以反复套用的模板:“维护一个动态窗口,窗口内满足某种约束,用两个指针控制窗口的伸缩”。

这个模板大致长这样:

def sliding_window(arr): n = len(arr) left = 0 state = ... # 维护窗口状态的变量,可能是哈希表、计数数组、和为sum等 ans = 0 for right in range(n): # 把arr[right]加入窗口,更新state while 窗口不满足约束: # 把arr[left]移出窗口,更新state left += 1 # 此时窗口状态合法,用right-left+1更新ans return ans

这个模板可以解决的问题非常多。比如“无重复字符的最长子串”(约束:窗口内所有字符不同);“最大连续1的个数 III”(约束:窗口内0的个数不超过k);“替换后的最长重复字符”(约束:窗口内出现次数最多的字符的补集长度不超过k)。把水果成篮吃透,等于把这几个经典题的骨架都摸清了。你会在刷题过程中发现,很多Hard题的思考路径也跟这个模板有关,只不过约束条件更复杂、需要维护的状态更多。

5.2 从“最多两种”到“恰好两种”的变式

一个非常常见的变式是把问题改成:“恰好包含两种不同元素的最长子数组”。注意和“最多包含两种”的区别。“最多”允许窗口里只有一种元素,而“恰好”要求窗口里必须有两种元素,并且不能超过两种。

如果只在“最多”的代码上做简单修改,把答案更新条件改成len(basket) == 2时才更新,这在某些用例下会出错。例如数组[1, 1, 1],按“恰好两种”的语义,不存在任何包含恰好两种元素的子数组,答案应该是0。但如果只加一个==2的判断,窗口长度为3时basket大小是1,不会更新,答案保持0,这碰巧是对的。可如果数组是[1, 2, 2, 1],“最多”的答案是整个数组长度4,因为1和2正好两种,而“恰好”的答案也是4,这两种写法在这个例子上看不出差别。只有在全部元素相同时才有差异。

处理“恰好”类型更稳的做法是:先求“最多不超过k种”的最长长度,再求“最多不超过k-1种”的最长长度,两者相减即可。这个技巧在算法竞赛里叫“前缀和差量化限制”,但套到这里就是:恰好k种 = 最多k种 − 最多k−1种。因为“不超过k”的子数组集合减去“不超过k−1”的子数组集合,剩下的恰好就是“等于k”的子数组。这个式子成立的前提是子数组的长度性质是单调的,而子数组长度天然满足单调性。这个方法在刷题时非常通用,比如“恰好k个不同字符的最长子串”“恰好k个不同字符的子数组数量”等等,都可以用这个技巧避开通篇特判。

5.3 与LeetCode近期热题的横向联系

搜索热词里出现了“LeetCode周赛430”“LeetCode旅行商”“LeetCode 073爱吃香蕉的狒狒”等,这些虽然和水果成篮不是同一道题,但放在一起看能发现点规律。“爱吃香蕉的狒狒”属于“二分答案”题型,核心是先假设一个速度,再去检验是否能在规定时间内吃完;“旅行商”则属于“状态压缩DP”题型,与区间维护关系不大;而周赛里经常出现的“连续子数组”“最多k种元素”类题目,几乎都是今天讲的滑动窗口模板的变种。

我自己的刷题习惯是,每周周赛结束后,把其中涉及滑窗的题目和本题归到一起,统一复盘。比如如果有一道题是“求最长子数组使得子数组内最大值与最小值之差不超过k”,那维护的状态就从“种类数”变成了“最大值与最小值”,数据结构可能要换成双端队列(单调队列),但窗口伸缩的主框架还是不变。这种横向归纳远比闷头刷题有效。

5.4 双指针与滑动窗口的异同点分析

滑动窗口属于双指针技术的一种,双指针还有一种常见形态是“相向双指针”,主要用于有序数组上的两数之和、三数之和、盛最多水的容器等问题。而滑动窗口属于“同向双指针”,两个指针都只朝一个方向移动,不会回头。这种单向运动保证了均摊复杂度是线性的。

理解这一点对面试很有用。面试官追问“你的算法为什么是O(n)”时,可以这样回答:因为每个元素最多被右指针加入一次、被左指针移出一次,总操作次数不超过2n,所以整体线性。这个论证在滑动窗口中几乎是万能模板。

6. 实测过程与性能对比记录

6.1 本地性能测试与数据集设计

为了验证两种实现的性能差异,我在本地做了一组对比测试。测试平台为macOS(Apple Silicon),Python 3.11,LeetCode的评测环境与本地有一定差异,但相对趋势仍能说明问题。

测试数据构造思路:生成一个长度为200万、元素值在0到9之间随机分布的大数组,这样哈希表的key数量最多10个,理论上哈希表操作非常高效,碰撞也很少。如果在这种情况下数组法仍能胜出,那就说明常数项的优势是真实存在的。

实现方式数组长度运行耗时(三次取均值)相对差异
哈希表dict版2,000,0000.83 s基准
计数数组版2,000,0000.51 s快约38%
计数数组版(带局部变量复用)2,000,0000.47 s快约43%

这个结果在意料之中。Python的dict虽然很快,但每次get、set都要走一套哈希流程,而数组下标访问直接就是指针偏移。当key值域有限时,用数组替代哈希表是常见且有效的优化手段。

另外注意一个小技巧:Python里在while循环内部反复写basket[left_fruit]这种嵌套下标访问,会有属性查找开销。把fruitscountans都绑定到局部变量,能提升一些速度。虽然单次操作差距很小,但在两百万级的循环里,累积起来效果就明显了。

6.2 LeetCode提交结果与复杂度复盘

在LeetCode上,我用C++哈希表版本提交了一次,运行时间约60ms;用C++计数数组版本提交,运行时间约40ms。内存占用上,哈希表版约60MB,计数数组版约10MB。虽然LeetCode的计量方式会随服务器负载波动,但数组法在内存上确实占了压倒性优势,因为不需要为哈希表的桶分配大量内存,只需要开一个足够长的数组。

空间复杂度方面,哈希表版理论上限是O(k),k是数组里不同元素的个数,最多不超过n;计数数组版是O(max_val),max_val是数组元素值的最大值,题目一般会给出约束,比如0 ≤ fruits[i] ≤ 100000。如果值域很大(比如10^9),计数数组法就不适用了,哈希表才是合理的方案。所以选哪种实现,主要看题目对值域的约束。

6.3 实测中出现的隐藏Bug:收缩循环写成if

我在第一次写这题的时候,犯过一个非常蠢但很经典的错误:把收缩的while写成了if。题目明明可能一次要收缩好多个元素,才能把多余的水果种类移除,我却只收缩了一次就继续更新答案,结果窗口里始终有三个key,答案一直不对。

后来我总结出一条自查经验:当窗口内状态“超标”时,收缩用while;当窗口内状态有可能“一次性达标”时,收缩才可以用if。水果成篮这里,删除一个左元素后,可能size还是3,必须要循环删除。判断的依据是:收缩操作的“单步最小效果”能否保证窗口恢复合法,如果不能,就必须用while

6.4 性能优化并不总有必要

刷题时有些人过度追求最优解,连常数优化都要抠。但实际面试场景里,能写出清晰、正确的哈希表版本,并且能解释清楚复杂度,已经足够拿到不错的评价了。计数数组版本可以作为“追问时的亮点”抛出来,展示你对工程性能的敏感度,但不要在第一步就写它,因为万一题目没给值域范围,你的数组长度都不知道该开多大。

一个稳妥的写法是:先写哈希表版本,并在注释里说明“如果值域有限,可改用计数数组优化”,然后口头解释一遍两种方案的取舍。这比一上来就写数组法更安全,也更显得思考全面。

7. 从“会做”到“会讲”:面试中的表达策略

7.1 先讲思路,再讲代码

面试时如果抽到这题,不建议直接上手写代码。先用30秒到1分钟的时间把思路讲清楚:维护一个窗口,窗口内最多只能有两种水果,用哈希表记录窗口内每种水果的数量,右指针逐一向右扩展,一旦种类超过2,左指针收缩直到恢复合法,期间用窗口长度更新答案。讲完思路再写代码,会让面试官觉得你有结构化的思维。直接埋头写代码,即使写对了,交流分也会打折扣。

我见过不少候选人,代码写得飞快,但问他“为什么收缩条件是basket.size() > 2而不是>= 2”时卡住了。答案是:题目要求最多包含两种,所以恰好包含两种是合法状态,不需要收缩。这个细节看起来无关紧要,恰恰暴露了对“合法状态”边界的理解程度。所以讲思路的时候,最好连“为什么是>而不是>=”也一并说了。

7.2 复杂度分析的表述技巧

时间复杂度讲O(n),空间复杂度讲O(k)。这里的k不是数组长度,而是窗口内水果的最大种类数。本题中由于最多只允许两种水果,理论上k最大就是2,所以空间复杂度可以更严格地说O(1)。哈希表里最多只会有2个key,因为一旦插入第三个,立刻就会触发收缩,删掉一个key,恢复为2个。这就引出了一个很有趣的性质:本题的哈希表大小恒不超过2。

这是题目约束带来的天然优化空间。面试时可以说:“因为最多只有两种水果,所以哈希表的大小最多为2,空间复杂度实际上是O(1)。”这句话比单纯说O(k)更能说明你对题目的理解深度。

7.3 举一反三的思考过程演示

最后,面试官大概率会追问:“如果把最多两种改成最多k种,你怎么改?”直接改法很简单:把basket.size() > 2改成basket.size() > k即可。时间复杂度还是O(n),空间复杂度O(k)。如果把问题改成“恰好k种”呢?那就用“最多k种减去最多k−1种”的技巧。如果把问题再改成“每一种水果的采摘数量等于其出现次数”,那就完全是另一个问题了,需要用到前缀和加哈希表计数。

这种“改约束”的追问方式,面试官其实在测试你是否真的理解了算法的本质。你能快速答出来,说明你理解的是“滑动窗口适应约束条件”的通用框架,而不是死记硬背当前这题的代码。

8. 实用小技巧与刷题资源扩展

8.1 手写哈希表计数时的三条纪律

第一,所有插入操作都必须走同一套逻辑,加计数或新增key,不要在不同分支里写两遍;第二,所有删除操作都必须遵循“计数先减1,为0再删key”,除非能证明某种情况下可以直接删key;第三,更新答案的位置固定放在收缩循环之后,不要在多个地方重复写。这三条习惯能让你在写任何哈希表计数类滑动窗口题时,少出至少一半的bug。

8.2 刷题顺序建议

如果你正在刷LeetCode热题100或滑窗专题,推荐按这个顺序练习:先做“无重复字符的最长子串”,再做“最大连续1的个数 III”,然后是这题“水果成篮”,最后是“替换后的最长重复字符”。前两题帮你建立“右进左出”的基本感觉,这一题帮你加深“计数哈希表维护状态”的能力,最后一题则让你体验“状态比较复杂时如何更新答案”。

8.3 一些值得留意的LeetCode近期动向

热词里提到的“LeetCode周赛430”说明大家最近都在跟周赛节奏。周赛的题目风格和题库里有些区别,更强调在规定时间内快速识别考点并写出简洁代码。水果成篮这类经典题虽然不一定直接在周赛中出现,但它的小变种很常见,比如“最大连续子数组,使得子数组内不同元素个数不超过k”“乘积小于K的子数组”等,都属于同一个滑窗家族。把滑窗模板练扎实,周赛T2、T3遇到类似题会从容很多。

另外热词里的“LeetCode旅行商”属于比较综合的题目,跟滑窗关系不大,但如果你打算冲周赛高分,动态规划、状态压缩这些专题同样不能落下。滑窗题练的是一种“局部状态自动维护”的直觉,这种直觉在DP里也有用,两者并不冲突。

8.4 最后再分享一个我认为最有用的技巧

如果你调试滑动窗口代码时发现结果死活不对,别急着看题解。先写一个暴力解作为“标准答案”,然后用随机小规模数据做对拍:不断生成随机数组,把暴力解结果和滑动窗口结果对比,一旦出现不一致,把那一组数据打印出来。这个数据会非常精准地告诉你窗口收缩或更新答案的逻辑在哪一步出了问题。我在刷算法题时,有一半以上的疑难杂症是用这个方法定位解决的。它不光是针对这一题,几乎所有数组类算法题都适用。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/17 3:21:21

亿级排行榜架构设计:Redis ZSet分片与冷热分离实战

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/17 3:19:50

GPT-5.2Pro证明埃尔德什猜想?陶哲轩:陷阱存在但AI未犯错

大概两天前&#xff0c;我刷到一条让我在电脑前愣了好一阵的消息&#xff1a;GPT-5.2Pro声称独立证明了一个悬置45年的数论猜想——埃尔德什猜想。更抓眼的是&#xff0c;菲尔茨奖得主陶哲轩转发了相关讨论&#xff0c;原话大意是“其中存在陷阱&#xff0c;但AI没犯错”。这组…

作者头像 李华
网站建设 2026/9/17 3:19:23

WinApps 快速上手:3 步在 Linux 上原生运行 Office 等 Windows 应用

WinApps 快速上手&#xff1a;3 步在 Linux 上原生运行 Office 等 Windows 应用 【免费下载链接】winapps Run Windows apps such as Microsoft Office/Adobe in Linux (Ubuntu/Fedora) and GNOME/KDE as if they were a part of the native OS, including Nautilus integrati…

作者头像 李华