资讯详情

资讯详情

LeetCode 1224 最大相等频率:用哈希表与频率分布形态实现线性判定

LeetCode 1224 的 Maximum Equal Frequency最大相等频率是我刷题时印象很深的一道困难题。它名字很直白给一个正整数数组找出最长的一个前缀使得我们删除前缀中的一个元素后剩下的每个不同数字出现次数完全相同。我第一次做的时候被“删除一个元素”这个动作带偏了真的去枚举每个位置删除结果不仅超时还漏了好几种边界。后来才发现关键不在于删除哪一个而在于把“删除后频率相等”这个状态翻译成删除前的频率分布形态。这篇文章我会从题目语义、形态分类、线性维护、代码实现到边界坑点完整过一遍适合被这道题卡过的读者也适合准备周赛时想学一种通用套路的人。1. 题目到底在问什么1.1 返回的不是整个数组而是最长前缀先明确最基础的输入输出给定一个正整数数组 nums长度可以到 10^5要求返回一个最大的前缀长度 k使得前 k 个数组成的子数组满足条件删除其中一个数后剩余数组中每个不同数字的出现次数都相同。前缀必须从下标 0 开始连续取不能跳过中间元素更不能取任意子数组。很多人第一次读题会默认答案是“最长的满足条件的子段”但 LeetCode 1224 限定了必须是前缀这反而简化了很多因为我们只需要从头往后扫描不需要滑动窗口回退。LeetCode 官方给的经典例子是 nums [2,2,1,1,5,3,3,5]。整个数组长度是 8四个数字 2、1、5、3 每个都出现了 2 次看起来非常整齐。但删除一个元素后四个数字的出现次数会变成 1、2、2、2 这种不均匀的状态所以 8 不合法。再看前 7 个元素 [2,2,1,1,5,3,3]出现次数是 2:2次、1:2次、5:1次、3:2次。删掉唯一的 5 之后剩下的三个数字都出现 2 次满足条件所以返回 7。这个例子同时说明两个点第一前缀不是结果子序列答案必须从数组左边连续取第二即便整个数组已经做到了每个数字频率相同删除一个元素后可能反而不满足因为删除会打破原本的均衡。1.2 “每个元素出现次数相同”里的每个元素指什么这句话最容易产生歧义。这里比较的对象是不同数字之间的出现次数不是每个数字的每个个体。比如剩余数组 [1,1,2,2]数字 1 出现 2 次数字 2 出现 2 次所以频数序列是 [2,2]满足。剩余数组 [1,2,2] 中数字 1 出现 1 次数字 2 出现 2 次频数序列是 [1,2]不满足。再看一个例子 [2,2,1,1,5,3,3]删除数字 5 后剩余 [2,2,1,1,3,3]频数序列是 [2,2,2]满足。还有个小细节容易被忽略如果某个数字在删除后完全不出现了它就不在剩余数组中不要给它补一个 0 次。0 次不应该参与比较因为条件里说的是“每个不同数字在剩余数组中出现的次数”不存在于数组里的数字自然不在讨论范围内。这个细节对理解后面形态二特别关键删除唯一一个只出现一次的数字时这个数字会直接消失剩下的其他数字只要频率相等就合法不需要考虑那个已经消失的数字。1.3 为什么暴力法在这里会挂如果老老实实地对每个前缀做统计再尝试删除每个位置验证复杂度大约在 O(n^3) 到 O(n^2 log n) 之间n 到 10^5 时完全不可行。就算优化成“每个前缀只统计一次然后遍历所有数字模拟删除其中一个数字后的频率变化”最坏情况下每个前缀也要 O(distinct)整体还是 O(n^2)依然无法通过大数据。所以这道题真正的门槛不是题意而是如何用增量更新的方式在一次遍历里同时维护状态并判断合法性。这要求我们先把“合法状态”压缩成几个简单条件而不是每次真的去删元素。LeetCode 1224 的困难标签多半也是来源于此代码量很少但抽象过程很绕。2. 核心思路把“删除一个数字”翻译成频率形态2.1 用“频率的频率”看问题设 cnt[x] 表示数字 x 在当前前缀里出现几次。再设 freq[k] 表示“当前有多少个不同的数字它们的出现次数恰好是 k”。比如前缀 [2,2,1,1,5,3,3]cnt 是 {2:2, 1:2, 5:1, 3:2}freq 就是 {1:1, 2:3}含义是有 1 个数字出现了 1 次有 3 个数字出现了 2 次。这种“频率的频率”是这道题最关键的一层抽象。有了 freq我们不再关心具体是哪几个数字只关心出现次数的分布形态。删除一个元素只会让被删数字 x 的 cnt[x] 减 1也就是让 freq[old] 减少 1freq[old-1] 增加 1其他数字的 cnt 和 freq 完全不变。这样一来“删除一个元素后频数序列全相等”这个问题就等价于“频数分布经过一次从 old 到 old-1 的迁移后freq 里只剩一种非零 key”。从数字维度一下子压缩到了频率维度后面所有推导都基于这个视角。2.2 合法前缀只有三种形态设当前前缀长度是 n最大出现次数是 maxFreq。如果我们删掉一个元素后所有剩余数字的出现次数都相同记为 t那么删除前只可能有三种形态形态一所有数字都出现 1 次即 maxFreq 1。此时删除任意一个数字它消失剩余数字仍然都出现 1 次。形如 [1,2,3,4,5]。形态二有一个数字只出现 1 次其他所有数字都出现 maxFreq 次。删除那个只出现 1 次的数字它消失剩余数字全部出现 maxFreq 次。形如 [1,1,2,2,3]1 和 2 出现 2 次3 出现 1 次删除 3。形态三有一个数字出现 maxFreq 次其他所有数字都出现 maxFreq - 1 次。删除那个高频数字的一个元素后它也变成 maxFreq - 1 次于是所有数字频率相等。形如 [1,1,2,2,3,3,3]1 和 2 出现 2 次3 出现 3 次删除一个 3 后剩下 2、2、3。为什么没有第四种形态因为一次删除只会影响一个数字的频率。如果删除前频数分布超过两种删除一个数字最多只能消掉一种来源不可能让所有剩余数字频率相等。严谨一点说删除后所有数字频率同为 t那么删除前被删数字的频率只能是 t1删一个变成 t或者是 1删除后这个数字消失相当于 t 从 0 变成不存在。如果被删数字频率是 t1其他数字频率就必须是 t这就是形态三如果被删数字频率是 1其他数字频率必须是 t而被删数字消失这就是形态一或形态二。分类是完备的。2.3 三个形态的数学判定式用 maxFreq 和 freq 表来写表达式比用文字描述要严谨得多形态一maxFreq 1。形态二maxFreq * freq[maxFreq] n - 1。形态三freq[maxFreq] 1 且 (maxFreq - 1) * (freq[maxFreq - 1] 1) n - 1。形态二的推导所有达到最大频率的数字它们总的元素个数是 maxFreq * freq[maxFreq]。如果这个值正好占满了 n-1 个位置那么剩下的 1 个位置一定是某个只出现一次的数字。删除它后剩余元素全部来自 freq[maxFreq] 个数字每个数字出现 maxFreq 次于是相等。这里被删除的数字不是高频数字而是那个唯一的低频数字。形态三的推导只有一个数字 x 达到 maxFreq删除 x 的一个元素后x 贡献 maxFreq - 1 次。其他数字如果都要等于 maxFreq - 1那么它们总的元素数应该等于 (maxFreq - 1) * freq[maxFreq - 1]。删除后总长度是 n - 1所以要求 (maxFreq - 1) (maxFreq - 1) * freq[maxFreq - 1] n - 1也就是 (maxFreq - 1) * (freq[maxFreq - 1] 1) n - 1。反过来这个等式成立时也能反推出不存在其他频率更小的数字因为其他数字的总出现次数刚好被 freq[maxFreq - 1] 个数字填满没有余量给更低频率。2.4 把常见合法前缀套进判定式我习惯用几个具体例子验证判定式确认没有漏条件。[1,1,1]maxFreq 3freq[3] 1。形态三里 (3-1)(freq[2]1) 2(01) 2n-1 2成立。这对应删除一个 1 后剩余两个 1 都出现 2 次合法。[1,1,2]maxFreq 2freq[2] 1。形态二里 2*1 2n-1 2成立。这对应删除唯一的低频数字 2等等[1,1,2] 中 2 出现 1 次删除 2 后剩余 [1,1]数字 1 出现 2 次合法。[1,2,3,4,5]maxFreq 1形态一成立。注意形态二和形态三在这里都不成立所以形态一必须单独判断。[2,2,1,1,3,3,3]maxFreq 3freq[3] 1freq[2] 2。形态三里 2*(21) 6n-1 6成立。这对应删除一个 3 后三个数字都出现 2 次合法。[1,1,2,2,3,3,3,4,4,4]n 10maxFreq 3freq[3] 2形态二 3*2 6不等于 9形态三因为 freq[3] 2不满足。也确实无法通过删除一个元素让 1、2 变成 3 次或者让 3、4 中的一个变成 2 次后全局相等判定式正确地排除了它。3. 线性维护两个频率表3.1 增量更新 cnt 和 freq遍历数组时每遇到一个新元素 x先记下它旧的出现次数 old cnt[x]。如果 old 0说明之前已经有 x那么它原来贡献了一个“出现次数为 old”的数字现在这个数字要从 old 档位挪到 old1 档位所以 freq[old] 要减 1。然后更新 cnt[x] old 1freq[old 1] 加 1。如果 freq[old] 减到 0我习惯把键删掉因为后面的判定要用 freq[maxFreq - 1] 这类查询删掉后直接用 get 也能拿到 0不会出现残留脏数据。对于第一次出现的数字old 0不需要动 freq[0]直接让 freq[1] 加 1。这里有个容易写错的点老手也会把顺序搞反先加了 cnt 再去减 freq[old]结果把新频率减掉了。正确顺序是先减旧档位再更新 cnt再加新档位最后更新 maxFreq。每次更新时freq[old] 一定存在因为 old 对应的至少有一个数字 xfreq[old 1] 可能不存在用 get 默认 0 再加 1。整套操作在 Python 里用字典实现非常顺手注意不要写成freq[old] - 1而不做防御性判断那样遇到 old0 会把 freq[0] 减成负数。3.2 为什么 maxFreq 只增不减很多刚接触这个解法的读者会疑惑如果某个数字的出现次数一直是最大值但后来没有新增它的 freq 可能变成 0maxFreq 是不是要回退其实完全不需要。因为我们每次只把一个数字的出现次数加 1并不会减少任何已有数字的出现次数所以整个前缀的“最大出现次数”只可能维持不变或者变大。举例前缀 [1,1,2] 的 maxFreq 是 2加入 3 后变成 [1,1,2,3]maxFreq 仍然是 2加入 1 后变成 [1,1,1,2,3]maxFreq 变成 3。它永远不会从 3 掉回 2。因此每轮更新 maxFreq max(maxFreq, newFreq) 就足够了不需要额外维护次大值或者回退逻辑。这个性质是这道题能线性维护的重要前提。如果你在别的地方见过需要维护“当前出现次数前二大”的题目比如删除一个数后要保证剩余最大频率不超过某个阈值那种场景 maxFreq 才会回退。但 1224 的删除动作是在判断条件里模拟的而不是真的从数组里移除元素所以当前前缀的 maxFreq 只会随数组增长而单调非降。3.3 每一轮按顺序判定三个条件我的代码里每次加入一个元素后立即得到当前前缀长度 n然后依次检查三个条件。检查顺序建议是先检查 maxFreq 1再检查形态二最后检查形态三。形态一必须单独放在最前面因为当所有数字都只出现一次时freq[maxFreq] freq[1] n形态二的等式是 1*n n-1除非 n1否则不成立不能靠后两个条件覆盖。形态二和形态三在绝大多数情况下不会同时成立即使同时成立结果都是满足条件不影响答案。有些人喜欢用 if 和 elif 串起来有些人用独立的布尔变量。我习惯先设 ok False然后逐条判断只要满足就 ok True最后如果 ok 就更新答案。这样调试时能看到每个条件是否命中不至于把多个分支搅在一起。3.4 时间复杂度与空间复杂度这个解法只需要一次遍历每个元素更新 cnt 和 freq 都是 O(1)整体时间复杂度 O(n)n 最大 10^5非常轻松。空间上cnt 需要存不同数字的计数最坏情况下每个元素都不同cnt 大小 O(n)freq 的 key 是出现次数最大值不会超过 n但用哈希表时也是 O(n)。如果题目明确 nums[i] 范围很小比如 1 到 10^5可以开定长数组替代哈希表速度更快。但通用解法用哈希表最稳因为题目没有保证数字范围一定小。我实测 Python 里用 dict 和 defaultdict 差距不大反而要小心 defaultdict 在查询不存在的 key 时会自动创建键干扰后续逻辑。所以这里我更推荐普通 dict 加 get。4. 代码实现与测试用例4.1 Python 完整实现下面是用 Python 写的完整版本。我特意不用 Counter 的 most_common 之类的技巧把每一步拆开写方便你对着上面的推导检查。代码里除了解释行之外核心逻辑只有二十多行。from typing import List class Solution: def maxEqualFreq(self, nums: List[int]) - int: cnt {} # 数字 - 出现次数 freq {} # 出现次数 - 有多少个数字恰好是这个次数 max_freq 0 ans 0 for i, x in enumerate(nums): n i 1 old cnt.get(x, 0) if old 0: freq[old] - 1 if freq[old] 0: del freq[old] new old 1 cnt[x] new freq[new] freq.get(new, 0) 1 max_freq max(max_freq, new) ok False if max_freq 1: ok True elif max_freq * freq[max_freq] n - 1: ok True elif freq[max_freq] 1 and (max_freq - 1) * (freq.get(max_freq - 1, 0) 1) n - 1: ok True if ok: ans n return ans这里有几个实现细节需要强调。第一freq[old] 减到 0 时删除键之后freq.get(max_freq - 1, 0)就足够安全。第二freq[max_freq] 一定有键因为 max_freq 至少来自当前新增的那个数字它已经让对应频率的计数加了一所以可以直接用中括号。第三形态二和形态三的 n - 1 都写的是当前前缀长度减一千万别写成 ii 是从 0 开始的下标当前长度是 i 1。4.2 测试用例逐条解释我建议刷题时准备一组覆盖不同情况的数据提交前在本地跑一遍。这里列几个典型的。输入输出原因[1]1删除唯一元素后为空只有一种数字可以认为满足[1,1]2删除任意一个 1剩余一个 1频率相同[1,1,1]3删除一个 1剩余两个 1仍只有一个数字[1,2]2删除任意一个剩余单个数字频率相同[1,2,3,4,5]5所有数字出现频率都是 1删除任意一个即可[2,2,1,1,5,3,3,5]7前缀 8 不满足前缀 7 删除唯一的 5 后剩余频率都是 2[1,1,1,2,2,2,3,3,3,4,4,4,5]13删除唯一出现 1 次的 5其余数字频率均为 3[1,1,2,2,3,3,3]7删除一个 3 后1、2、3 出现次数均变为 2[1,1,1,2,2,3]不返回 6删除 3 后频率为 3、2删除一个 1 后频率为 2、2、1均不均衡最后一行我写“不返回 6”是为了提醒不是所有看起来接近均衡的数组都满足。测试时把这种反例放进去能有效检验条件是否过拟合。你在本地可以用 true 或 false 判断某个前缀是否满足而 LeetCode 要求的是最长前缀长度所以要用一个变量记录最近一次满足的 n。4.3 其他语言的实现要点C 和 Java 的实现思路完全一样只是数据结构写法不同。C 可以用 unordered_map 或者 vector。如果 nums[i] 的范围小用 vector 更快cnt 开成数字最大值 1freq 开成 n 1max_freq 是整数。但题目并没有保证值域所以最稳还是 unordered_map。C 里更新 freq 时要注意freq[old]在 old0 时不能直接减要判断 old 0。另外查询 max_freq - 1 时如果直接用freq[max_freq - 1][]运算符会自动插入一个默认 0 的键会影响循环次数但不会影响结果判断。如果在意用freq.find(max_freq - 1) ! freq.end()或者 C17 的if (auto it freq.find(...); it ! freq.end())。Java 使用 HashMapInteger, Integer更新时需要用getOrDefault。删除键为 0 的项可以用remove(key, 0)这个方法它会仅当当前映射值等于 0 时删除非常方便。如果你想把 freq 数组化因为出现次数最大值不会超过数组长度 n可以直接int[] freq new int[n 2];然后freq[old]--和freq[new]省去很多空指针判断。4.4 答案初始值设定为 0 还是 1我的代码里答案初始值是 0但数组长度至少为 1遍历第一轮时 maxFreq 1就会把 ans 更新成 1。所以从 0 开始是安全的。也有题解习惯把 ans 初始化成 1因为只有一个元素时无论如何都满足。两者都可以但如果你把 ans 初始化成 1要注意一些极端情况比如测试用例只有一个元素那么循环里即使我漏掉了判断答案也正确反而会掩盖 bug。所以我更推荐答案初始 0让第一轮判断真实执行一旦漏条件就能暴露出来。还有一个容易误解的点长度为 1 的前缀删除一个元素后是空数组“每个元素出现次数相同”这个命题在空集上通常被认为是真LeetCode 的数据和标准题解也接受了这个情况。所以答案至少会是 1。如果实在不放心在代码开头加一句if len(nums) 1: return len(nums)也没问题只是对最终逻辑没影响。5. 常见问题与排查技巧5.1 为什么我的答案总是少 1最常见的原因是漏掉了 maxFreq 1 这个条件。比如 [1,2,3,4,5]maxFreq 1freq[1] 5。形态二的等式是 15 5不等于 4形态三的等式左边是 0(freq[0]1) 0也不等于 4。如果只写了形态二和形态三这种全不相同的数组永远判不合法答案就会停留在之前某个较短的位置。另一个常见原因是把 n 写成了 i。比如当前前缀长度是 7但 i 是 6判断式里用 i-1 自然少 2答案也跟着偏小。editing 这种下标问题在 Python 里尤其容易发生因为 enumerate 给的是从 0 开始的下标。5.2 更新 freq 顺序写错的坑错误版本可能是这样先cnt[x] 1再freq[old] - 1最后freq[new] 1。表面看影响不大但实际会出问题。假设 x 之前出现 1 次freq[1] 是 1。先更新 cnt[x] 2此时 freq[1] 还没动再执行 freq[old] - 1实际上是在把“出现 1 次的数字数量”减一。这个动作本身是对的但如果之后又执行 freq[new] 1而 new 也是 2逻辑也能对上。真正的坑在于如果 old 对应的键已经减到 0你没有删除它而后面的判断又依赖 freq[maxFreq - 1] 这样的值残留的 0 不会造成错误但在调试打印时很难看。更严重的问题出现在 old 0 且你写了freq[old] - 1。这时会把 freq[0] 减成 -1虽然 0 档位不参与判断但对后续 key 的清理造成干扰。所以我建议严格区分old 0 时跳过旧档位更新old 0 时再减旧档位。这类顺序问题靠背诵容易忘最好的办法是每次更新后打印 freq 和 cnt跑一组短数据肉眼看档位变化是否符合“从 old 挪到 old1”。5.3 条件判断要不要用 getPython 中freq[maxFreq]一定有 key因为 maxFreq 至少由当前新更新的数字产生freq 里必然会存在它。但freq[maxFreq - 1]不一定存在需要用freq.get(maxFreq - 1, 0)。如果直接freq[maxFreq - 1]当这个键不存在时会在判断时插入一个新键值为 0。虽然不会直接让结果出错但插入了新键会导致后续循环里 freq 的键越来越多影响性能也容易在调试时造成困惑。同理freq[old]在 old 0 时理论上是存在的但如果前面某轮更新时没有删除归零的键它也可能存在且值为 0。用freq[old] - 1会把 0 减成 -1产生脏数据。所以我建议每次减完旧档位后如果值等于 0 就删除键。这样后续所有访问都更安全。还有一个隐藏问题如果你用defaultdict(int)去存 freq那么查询一个不存在的键会自动创建为 0然后判断表达式里出现freq.get(max_freq - 1, 0)还好但如果写成freq[max_freq - 1]会在判断时把键插入所以我在这里偏好普通 dict。5.4 本地对拍先写暴力验证推荐一个非常实用的做法写一个 O(n^2) 的暴力验证函数随机生成 nums和线性解法跑同样的样例确保条件不漏。暴力的判断可以这样写对每个前缀统计 Counter枚举要删除的数字也就是遍历 cnt 的 key模拟 cnt[x] - 1然后看剩余非零频数是否全部相等。这种暴力虽然慢但用来生成正确答案非常可靠。跑一两百组随机数据很容易发现公式漏洞。我自己做这道题时就是因为写了暴力对拍才找到形态二少写条件的 bug。如果只靠 LeetCode 的十几个测试用例很可能带着错误思路赛后才发现。对拍脚本不需要提交只放在本地编辑器里数据规模设小一点比如 n 不超过 10随机生成 1000 组线性解法和暴力结果一致后再提交心里会踏实很多。6. 从这道题学到的通用套路6.1 “删除一个元素使所有频率相等”的同类题LeetCode 2423 是“删除字符使频率相同”思路几乎一样只不过针对字符串且返回布尔值。掌握 1224 的形态分类后2423 就是它的固定长度版本。另外 LeetCode 周赛里出现过很多“操作一个位置后让数组或字符串满足某性质”的题通常先把目标状态分类成少数几种再用哈希表或计数数组维护。1224 的困难点不是代码量而是你能不能想到用“频率的频率”来表示状态。想通了代码不到 30 行甚至可以迁移到 2423 的题解里。还有一类更常见的题比如“最少删除几个字符使频率唯一”需要贪心调整 freq 表。这类题的核心也是先统计 cnt 再统计 freq只不过操作从“删一个元素”变成了“删除多个字符”判定逻辑变成了“频率不能重复”。刷题时把这几道放在一起对比你会发现 1224 建立的状态抽象能力是后面很多中高难度题的基石。6.2 为什么会想不到“频率的频率”这个状态刷题时遇到“删除”“翻转”这类操作先不要急着模拟操作而是问自己操作前后的状态空间能否压缩删除一个元素影响的不是一个元素的“内容”而是它的出现次数进而影响的是出现次数的直方图。用一个 freq 表去记录直方图是最自然的状态压缩。这个思维模型在字符串、数字数组、树形结构里都适用。很多人一开始会盯着 cnt 表试图判断“有没有哪个数字出现次数不同”这样也能推但很容易漏。换成 freq 表之后所有判断都变成对少数几个整数的比较逻辑瞬间清晰。这就是为什么我反复强调“频率的频率”它把原本散落在不同数字上的状态聚合成了几个档位的数量。当你下次遇到“操作一个元素后全局性质”的题目时先想想能不能构造一个直方图来承载这个性质。6.3 我踩过的一个坑第一次写的时候我把形态二写成了freq[maxFreq] 1 maxFreq * freq[maxFreq] n - 1多加了一个“最大频率数字唯一”的条件。结果遇到 [2,2,1,1,5,3,3] 这种多个数字同时出现 maxFreq2 的情况形态二其实合法但我误判为不合法答案少了一段。后来才明白形态二删除的是低频数字不是高频数字所以不需要高频唯一。类似的形态三才要求高频唯一。这种“高频唯一”和“低频唯一”的差异非常容易搞混建议把两个条件写在注释里删除哪个、删除后变几档、剩余几档。再看形态二它其实允许高频数字有多个因为删除的对象是低频的那个数字。比如 [1,1,2,2,3]1 和 2 都出现 2 次删除低频 3 后剩下的 1 和 2 还是 2 次相等。所以形态二里 freq[maxFreq] 完全可以是大于 1 的。而形态三删除的是高频数字如果高频数字有多个删掉一个后被删的那个数字变成 maxFreq-1另一个高频数字仍然 maxFreq两者就不相等了因此形态三必须要求 freq[maxFreq] 1。6.4 做题时的极端用例清单写完代码以后我习惯手推几个极端用例全相同比如 [5,5,5,5]全部不同比如 [1,2,3,4,5]只有两个数字比如 [1,1,2,2,3]最大频率对应多个数字比如 [1,1,2,2,3,3]低频数字有多个比如 [1,1,2,2,3,4]。每个用例都要确认答案是否符合直觉。全相同的情况最容易让人误判比如 [5,5,5,5]删除一个 5 后剩下三个 5只有一个数字按定义所有剩余数字的频率都是 4等等这里要小心。删除一个 5 后剩余数组里数字 5 出现 3 次由于只有一种数字频数序列是 [3]所以是相等的。因此 [5,5,5,5] 答案是 4。而 [1,1,2,2,3,3] 这种每个数字都出现 2 次删除任意一个后三个数字的频率变成 1、2、2不相等所以不满足。把这些极端情况跑完再提交基本不会翻车。我个人在实际操作中还有一个习惯如果时间允许先把三个判定式写在纸面上对着某个随机数组逐轮推导而不是只依赖代码看懂。因为 LeetCode 这类困难题的难点往往不在编码而在分类讨论是否完备。把形态一、形态二、形态三的逻辑彻底理解清楚比背下代码重要得多。
觉得有用,分享给同行:

为您的企业打造数字门面

稳重轻奢商务风格,端正雅致视觉,长效耐看不易过时。

立即咨询 →