长度2N数组中找重复N次元素:鸽巢原理与固定窗口解法

刷算法题的时候,最烦的就是那种“看着简单,一写就错”的题目。今天要聊的这道“在长度 2N 的数组中找出重复 N 次的元素”,就是典型的代表。它出现在不少公司的笔试题单里,难度标记不高,但解法里藏着一个非常巧妙的思维模型。如果你只会用哈希表硬怼,那确实有点浪费这道题的价值。

先花十秒钟看清题目条件:数组长度为 2N,其中有一个元素恰好重复了 N 次,剩下 N 个元素互不相同,让你找出这个重复 N 次的元素。注意,题目没说数组有序,也没说元素是整数范围内的连续值,更没说你可以额外开多大的空间。从笔试角度说,这题考的不是你会不会用unordered_map计数,而是你能不能从“重复 N 次”和“长度 2N”这两个数字里读出潜台词。

这篇文章我会从题目约束的拆解讲起,依次给出暴力解、优化解、最优解三种方案,重点讲清楚为什么“窗口大小为 3”就能覆盖所有情况,然后补充正确性证明、常见卡壳点排查,以及这个思维模型在其他题目里的迁移方式。无论你是刚刷题的新手,还是准备面试的老手,这题都值得花二十分钟彻底吃透。

1. 题目约束里的数学气息:2N、N 次、互不相同的含义

先说结论:这道题真正难的地方不是找重复元素,而是你能不能意识到“重复 N 次”和“其余 N 个元素互不相同”这两个条件叠加在一起之后,会产生一个非常强的结构限制。我最早刷这题的时候,第一反应就是排序或者计数,压根没往数学上想,结果被一个“窗口长度固定为 3”的解法秒杀,心态直接崩了。

1.1 三个关键条件的叠加效应

拆开看这三个条件:

  • 数组长度是 2N,说明数组里一共有 2N 个位置。
  • 有一个元素重复了 N 次,说明这个元素占掉了数组一半的位置。
  • 其余 N 个元素互不相同,说明剩下的位置被 N 个不同的值填满,任何一个非重复元素最多出现一次。

把这三句话拼在一起,你能推出什么?重复元素出现的频率是 1/2,而且是唯二可能超过一次的“高频元素”。数组中除了它,其他所有元素都是“孤家寡人”。这意味着什么?意味着你在数组中任意取两个位置,如果这两个位置上的值相同,那么它们必然都是那个重复 N 次的元素——因为其他元素根本没有机会出现两次。

这个推论看起来平淡无奇,但它是后面所有优化解法的地基。哈希表解法本质上就是在利用这个性质:只要某个值第二次出现,直接返回它,不需要等统计完整个数组。随机抽样解法也是基于“元素占一半,抽到它的概率是 50%”这个事实。而最优解里的鸽巢原理,更是把这个性质逼到了极致。

1.2 为什么这题不能用“找出现次数最多的元素”的套路硬解

如果你拿这道题去套“多数元素”的经典解法(摩尔投票),你会发现它并不是最合适的。摩尔投票解决的是“出现次数大于 n/2 的元素”,前提是众数存在且严格过半。这里重复元素刚好占 1/2,恰好等于一半,不是大于一半。虽然从频率上看它已经是最高的,但“最高频”和“严格过半”是两个不同的概念,摩尔投票的正确性证明依赖“计数会相互抵消”,在等于一半的情况下,边界处理会变得非常恶心。

同样的道理,也不能把问题转化成“找到唯一出现多次的元素”后用异或处理。异或适合处理“只有一个元素出现奇数次,其他都出现偶数次”的场景,这题里其他元素是出现 0 次或 1 次,不是偶数次,直接异或会把结果搞乱。所以,这题的约束条件决定了它的最优解法不在“比拼频率”这个思路上,而是在“利用物理位置关系”上。这就是为什么后面要引入窗口和鸽巢原理。

2. 解法演进:从暴力哈希到 O(1) 空间的鸽巢原理

我刷题有个习惯,拿到一道题先写最暴力的解法,确保思路正确,然后再一步步优化。这样做的好处是,你能明确感知到每一步优化到底在解决什么问题。下面按我的思考路径依次给出三种解法。

2.1 解法一:哈希表计数,5 分钟就能写出来的保底方案

最不用动脑的方法就是遍历数组,用一个哈希表记录每个元素出现的次数,遇到某个元素次数达到 N 就直接返回。

int repeatedNTimes(vector<int>& nums) { unordered_map<int, int> cnt; for (int x : nums) { cnt[x]++; if (cnt[x] == nums.size() / 2) return x; } return -1; // 题目保证有解,不会到这 }

这段代码的时间复杂度是 O(N),空间复杂度是 O(N),因为哈希表在最坏情况下要存下 N 个不同的值加上那个重复元素。笔试中这个答案可以拿满分,毕竟题目没限制空间,而且大多数面试官会先接受这个方案,再问你“能不能优化空间”。

但注意一个细节:我提前判断了cnt[x] == nums.size() / 2,而不是等整个数组遍历完再找最大值。这是利用题目条件“重复元素恰好出现 N 次”做剪枝。因为长度为 2N,一旦某个元素计数达到 N,它就是我们要找的元素,不需要继续遍历。这个小优化虽然不改变复杂度量级,但能减少平均遍历次数,也体现了你确实读懂了题目约束。

2.2 解法二:随机抽样,用概率换空间

如果你看过某些公司的面经,可能会发现还有随机抽样的解法。思路很简单:元素占了数组一半的位置,那你随机抽一个下标,抽到重复元素的概率是 50%;随机抽两个不同的下标,至少有一个是重复元素的概率是 75%。按这个思路,随机抽两个位置,如果值相同就直接返回,如果不同就再抽。数学期望上抽几次就能命中。

int repeatedNTimes(vector<int>& nums) { int n = nums.size(); while (true) { int i = rand() % n; int j = rand() % n; if (i != j && nums[i] == nums[j]) return nums[i]; } }

这个解法空间复杂度是 O(1),代码也非常短,但有个问题:最坏情况下可能永远抽不到两个相同元素,虽然概率极低。面试里你敢写这个方案,其实是想展示自己对概率和随机化的理解,但有些面试官会追问“你能保证一定在有限次内结束吗”,这就比较尴尬。所以它更适合作为思路拓展,不适合作为最终交付方案。我个人建议在面试时提一嘴“随机抽样的思路能到 O(1) 空间,但不够确定”,然后引出下面真正的 O(N) 时间 O(1) 空间解法,观感最好。

2.3 解法三:鸽巢原理 + 固定窗口,正解长这样

现在进入核心。重复元素在长度为 2N 的数组里出现了 N 次,剩下 N 个位置被互不相同的元素占满。现在想象你把所有重复元素的位置标记出来,一共有 N 个标记,分布在这个长度为 2N 的数组里。因为非重复元素不会重复,那么任意两个相邻位置,如果值相同,必然是重复元素。但问题来了:重复元素的 N 个位置并不一定相邻,它们可能被其他元素隔开,比如一个隔一个,像[A, X, A, Y, A, Z, ...]。这种情况下,你检查相邻位置是找不到两个相同元素的。

这时候就需要鸽巢原理出场了。把数组分成 N 个长度为 2 的小组(位置(0,1),(2,3),(4,5)...)。因为重复元素有 N 个,但只有 N 个小组,如果每个小组最多放一个重复元素,那么 N 个小组刚好每个小组都放了一个。这本身不矛盾。但如果某个小组放了两个重复元素,那么这两个元素相邻,你直接检查相邻位置就能发现。麻烦的恰恰是“每个小组恰好一个”的情况,比如[A, X, A, Y, A, Z]这种交替排列。

关键来了:如果每个长度为 2 的小组里都恰好有一个重复元素,把小组内部的顺序打乱,会发生什么?比如第一个小组(A, X),第二个小组(A, Y),第三个小组(Z, A)。你会发现,只要重复元素在相邻小组里出现的位置差异不超过一个下标,那么隔一个位置的地方(下标差 2)就会出现两个相同的重复元素。换句话说,要么相邻两个位置(下标差 1)相同,要么隔一个位置(下标差 2)相同,要么首尾两个位置相同——这三种情况至少发生一种。这个结论就是窗口大小为 3 的由来。

严谨点说:把数组看作环形结构,重复元素出现 N 次,分布在 2N 个槽位里。如果任意两个重复元素之间的距离都大于等于 3(即下标差至少为 3),那么 N 个重复元素至少需要1 + 2*(N-1) = 2N - 1个槽位才能放下(第一个元素占 1 个槽位,之后每个元素与前一个相隔至少 2 个空槽位,所以每个额外元素需要 2 个新槽位)。但这和数组只有 2N 个槽位矛盾,因为2N - 1 > 2N不成立,恰好等于2N - 1是允许的?等一下,仔细算:2N - 1确实小于等于2N,所以这个推导还不够强。正确的推法是:如果任意两个重复元素的距离都大于 2(即下标差至少为 3),则 N 个重复元素占据的位置跨度至少为1 + 3*(N-1) = 3N - 2,当 N >= 2 时,3N - 2 > 2N恒成立(因为3N - 2 - 2N = N - 2,要 N > 2 才行)。当 N = 1 时数组长度是 2,只有一个元素出现一次,其他一个元素一次,这不符合“重复 N 次”的设定,因为 N=1 时重复 1 次就是出现 1 次,构不成“重复”,题目实际要求 N >= 2。所以当 N >= 2 时,必然存在两个重复元素的下标差不超过 2。

下标差不超过 2,意味着什么?意味着在数组中存在两个相等的元素,它们在原数组中的距离是 1 或者 2。所以只要检查所有下标差为 1 和 2 的位置对,如果发现相等就返回。但还有一个边界情况:如果数组是循环的,距离 2 还要考虑首尾相接的情况,不过更稳妥的做法是直接把首尾两个元素单独比较一次,覆盖环形距离为 1 的情形。于是标准解法就是两层循环:第一层比较相邻元素(i, i+1),第二层比较间隔一个的元素(i, i+2),最后比较(0, n-1)。

int repeatedNTimes(vector<int>& nums) { int n = nums.size(); for (int i = 0; i < n - 1; i++) { if (nums[i] == nums[i + 1]) return nums[i]; } for (int i = 0; i < n - 2; i++) { if (nums[i] == nums[i + 2]) return nums[i]; } return nums[0]; // 此时必然是首尾相同 }

注意第二个循环结束后,如果还没返回,说明相邻和隔一位都没找到相同元素。根据上面的推论,唯一剩下的可能就是首尾两个位置相同,所以直接返回nums[0]即可。这个解法的代码量比哈希表还短,时间 O(N),空间 O(1),而且没有任何随机性,是完全确定的算法。

3. 为什么窗口大小是 3 而不是 4 或 5:完整推演与正确性证明

很多人看完上面的代码会有一个疑问:你检查了距离 1 和距离 2,凭什么不漏?距离 3 就不需要检查吗?这个问题问得非常好,因为答案并不是“直觉上够用”,而是有严格数学推导的。这节我展开讲清楚,顺便帮你在面试时能顶住追问。

3.1 反证法推演距离上限

假设数组中任意两个重复元素的下标差都大于 2,即最小距离至少为 3。把重复元素按从左到右的顺序记为p1, p2, ..., pN。为了不让任何两个重复元素距离不超过 2,我们必须让p2与p1至少相隔 3,p3与p2至少相隔 3,以此类推。那么pN - p1 >= 3 * (N - 1)。

因为数组长度为 2N,索引范围是 0 到 2N-1,最大跨度pN - p1 <= 2N - 1。所以3*(N-1) <= 2N-1,化简得3N - 3 <= 2N - 1,即N <= 2。也就是说,当 N >= 3 时,必然存在一对重复元素的距离不超过 2。这里有一个临界情况 N = 2:此时数组长度为 4,重复元素出现 2 次,其余 2 个元素互不相同。如果两个重复元素分别在下标 0 和 3,那么距离是 3,确实不存在距离 1 或 2 的重复对。但别忽略首尾比较:下标 0 和 3 在环形意义下距离是 1,所以首尾比较会命中。这就是为什么代码末尾要单独比较一次nums[0]和nums[n-1]。

把 N = 2 的情况代入,你会看到窗口检查逻辑依然成立。N = 1 的情况题目不会给,因为“重复 1 次”没意义,长度 2 的数组只有两个互异的元素,找不到“重复 N 次”的目标。所以实际上窗口大小 3 已经覆盖了所有合法输入。

3.2 把证明翻译成更容易理解的盒子模型

如果你觉得上面的不等式推导太抽象,我换个说法。想象数组是 2N 个连续的盒子排成一行,你要把 N 个红球放进盒子,每个盒子最多放一个红球,剩下的盒子放白球(互不相同的非重复元素)。题目说白球彼此都不同,所以白球不是重点,重点只是红球的位置分布。

现在问:有没有可能任意两个红球之间至少隔着两个白球?也就是红球间距至少为 3。假设可能,那 N 个红球需要占据的盒子跨度至少是“第一个红球 + 每加一个红球多占 3 个盒子”,总共至少1 + 3*(N-1) = 3N - 2个盒子。当 N = 2 时,需要 4 个盒子,数组恰好有 4 个,可能实现(比如红 白 白 红)。当 N = 3 时,需要 7 个盒子,但数组只有 6 个,不可能实现。当 N 更大时更不可能。所以 N >= 3 时,必然有两个红球距离不超过 2。而 N = 2 的唯一“间距为 3”排布是首尾各一个红球,首尾比较能覆盖。窗口大小 3 就在这个意义上够用了。

3.3 能不能把检查窗口缩到更小?为什么不行

你可能还想问:既然距离上限是 2,那我只检查相邻两个(距离 1)行不行?答案是不行,因为存在交替排列的情况,比如[A, X, A, Y],重复元素 A 出现在下标 0 和 2,相邻位置(0,1)是 A 和 X 不相同,(1,2)是 X 和 A 不相同,(2,3)是 A 和 Y 不相同。如果只检查相邻位置,就漏掉了 A。这就是窗口必须覆盖距离 2 的原因。

那距离 3 要不要检查?按照 3.1 的证明,N >= 3 时不需要,N = 2 时唯一可能漏掉的距离 3 情况会被首尾比较兜底。所以这个解法的完整检查集合是:所有相邻对、所有距离为 2 的对、以及首尾对。把这三类合并起来,正好覆盖了“距离不超过 2 或环形距离为 1”的所有可能命中场景。

3.4 正确性证明的最后一块拼图:找到即正确

还有一个细节需要确认:如果检查过程中发现nums[i] == nums[i+1],这个值一定就是重复 N 次的元素吗?是的,因为除了重复元素,其他所有元素都互不相同,任何一个非重复元素不可能在数组中出现两次。所以一旦找到两个下标不同但值相同的位置,这个值必然是那个占了一半位置的重复元素。这也是题目条件里“其余 N 个元素互不相同”的真正价值——它让“找到一对相同”和“找到目标元素”完全等价。

如果把“其余 N 个元素互不相同”这个条件去掉,上面的窗口解法立刻失效。比如数组[A, X, A, X]中 A 出现 2 次,但 X 也出现 2 次,符合“长度 4,某个元素重复 2 次”的字面描述,可一旦你找到(0,1)位置的 X 和 X,你就会误以为 X 是答案。所以做这道题时,务必先向面试官确认“其余元素是否保证互异”,如果题目没有这个条件,最优解就变了,得回到哈希表或者排序。这一点我会在后面的排查清单里再强调。

4. 实操中的边界情况和常见卡壳点排雷

算法题最有价值的部分往往不是“会写解法”,而是“知道哪里会写错”。这道题代码短,但坑点一点不少。我把这些年刷题、讲解时遇到的典型问题整理成一份排查清单,你对照着检查,能省不少调试时间。

4.1 数组越界:第二个循环的上界到底怎么取

新手最容易犯的错是第二个循环写成i < n,然后访问nums[i + 2]时越界。正确的是i < n - 2。但很多人搞不懂为什么不是n - 1。原因是你要访问i + 2这个下标,必须保证i + 2 <= n - 1,也就是i <= n - 3。因为循环条件是i < n - 2,即i最大取到n - 3,刚好满足。同理,第一个循环访问i + 1,条件是i < n - 1。如果你用 Python 写range(n - 2),效果是一样的;用 C++ 写for循环时尤其要小心别把<和<=搞混。

4.2 首尾比较的顺序问题

代码里第三段如果写成return nums[n - 1]或者return nums[0]都可以,因为首尾相等时两个值相同。但要注意,这个第三段不能省,否则 N = 2 且重复元素分布在下标 0 和 3 时会漏检。举个例子:nums = [2, 1, 3, 2],长度 4,2 出现两次,1 和 3 各出现一次。相邻对(0,1)值不同,(2,3)值不同;距离 2 的对(0,2)值不同,(1,3)值不同。所有循环检查都找不到答案,只有比较nums[0] == nums[3](2 == 2)才能命中。所以面试时背代码容易,但理解为什么要有这一步,才是真正加分的地方。

4.3 用哈希表解法时别踩的“次数判断”陷阱

哈希表解法里,如果你写成“先统计完所有元素,再找次数等于 N 的元素”,逻辑上没错,但代码会多一个循环。我个人更喜欢边统计边判断,因为题目保证解存在且唯一,一旦某个值计数达到 N 就可以提前返回。这里有一个容易让人困惑的点:N = nums.size() / 2,如果用size_t类型,size() / 2是整除,没问题;但如果你用int存数组长度,也记得用int n = nums.size()而不是直接拿size_t去和哈希表里的int比较,避免无符号整数比较引发警告。虽然刷题环境一般不报错,但好的编码习惯还是该有的。

4.4 随机解法的死循环风险和 rand() 的局限

如果你在面试里展示随机解法,一定要主动说明“这个解法不确定,只是预期复杂度 O(1)”。实际写的时候还需要避免i == j的情况,不然同一个位置和自己比较,如果元素恰好就是重复元素,那其实也能返回正确结果,但语义上不够严谨,比如i == j时nums[i] == nums[j]恒成立,会直接返回当前元素,这在任何情况下都是错的吗?其实不是。因为任意一个位置上的值,无论是重复元素还是非重复元素,都满足“和自己相等”,所以如果不加i != j限制,第一次随机抽样就会直接返回nums[i],而nums[i]可能不是重复元素。这是随机解法里最隐蔽的坑。要保证返回的一定是重复元素,必须要求i != j且两个位置值相同。

4.5 输入规模极小的情况

数组长度 2N,N 最小是多少?按题目语义“重复 N 次”,N 至少为 2。所以数组最小长度是 4。但如果你自己造测试数据,想验证 N = 2 的情形,务必包含[A, B, C, A]这种首尾重复的用例,以及[A, B, A, C]这种距离为 2 的用例,这样才能覆盖窗口解法的所有分支。我真见过有人只测了[A, A, B, C]这种相邻重复的用例,然后沾沾自喜说解法正确,结果一提交就挂。所以自测时要有意识地制造“最分散”的分布:让重复元素尽可能均匀地铺开,间距拉大,才能触发首尾分支和距离 2 分支。

5. 从这道题抽象出来的思维模型:鸽巢距离窗口

这道题做完就完了吗?不是的。它的价值在于抽象出“鸽巢距离窗口”这个思维模型。很多看似无关的题目,底层逻辑都可以迁移过去。我给你列几个典型的例子,你自己品一品。

5.1 在“存在重复元素”类题目中的应用

LeetCode 上有一类基础题叫“存在重复元素”,问你一个数组里有没有任意两个元素相等。最直接的解法是排序后扫一遍或哈希表去重,但如果你把数组视作长度为 n 的序列,那么“找重复”本质上是在问“有没有哪两个元素的距离为某种模式”。当元素范围有限时,比如元素值都在[1, n]之间且部分缺失,你可以用鸽巢原理直接判断:n+1 个元素放进 n 个桶,必有重复。这类题的核心就是“元素数大于桶数,必定碰撞”,和 2N 数组里 N 个重复元素的推导是同一套逻辑。当你把重复元素看作“必须装进盒子里的红球”时,碰撞位置的上界就是窗口大小的理论来源。

5.2 与“长度为 n 的数组包含 1 到 n-1 的整数”这类题的联系

另一个经典套路是:一个长度为 n 的数组,元素值范围是 1 到 n-1,让你找重复值。这题最常见的是“原地标记”或者“快慢指针”解法。它的本质和本题一样:值域大小比数组长度小,至少有一个值要占用两个位置,所以重复大概率发生在某个局部范围内。快慢指针找环的做法,其实是把“下标跳转”模拟成链表,“重复值”对应“环入口”,也是鸽巢思想的一种体现。你先把 2N 数组这道题吃透,再去做“找环入口”那道题,思维衔接会非常顺畅,因为它们都在回答同一个问题:当资源数大于槽位数时,冲突发生在哪里。

5.3 二维场景下的迁移思考

如果把一维数组扩展到二维,比如在一个M x N的网格里,有某种元素超过了一半的格子,问你能否在 O(1) 空间内找到它。答案是可以的。因为它超过了一半,任意一个2 x 2的小窗口里必然至少出现一次,而相邻窗口会有重叠,你可以通过特定的扫描顺序锁定它。这个思想在图像处理里的“众数滤波”等场景也有应用。虽然刷题不会直接考二维版本,但理解了一维的推导过程,你就能自己设计出二维的模拟退火式搜索算法,这对思维训练非常有益。

5.4 实际工程里的应用场景

说个题外话,这种“固定窗口 + 鸽巢距离”的思想在工程上也有用。比如你在处理一个日志序列,想快速找出某个重复出现的高频事件,如果已知某个事件占了一半以上的时间戳,你可以只检查相邻时间戳和间隔一个时间戳的位置,而不需要维护全局计数器。这种方法的优势在于内存占用与事件种类无关,只与窗口大小相关,特别适合数据流场景。虽然生产代码里大家习惯用 Bloom Filter 或 HyperLogLog,但在某些嵌入式环境或者极度节省内存的场景下,这种确定性算法反而更香。

6. 面试答题节奏和代码演示建议

最后聊点面试向的东西。这道题在面试中经常作为“从暴力到最优”的考察题,面试官想看的不是你直接背出最优解,而是你如何一步步分析、优化、证明。我根据多次模拟面试的经验,给你一套比较稳妥的答题节奏。

6.1 先复述题意并确认约束

拿到题目,先别急着写代码。礼貌地确认:“数组长度是 2N,有一个元素出现 N 次,其余元素都互不相同,对吗?”这一句确认很关键,因为如果面试官临时把条件改成“其余元素可能重复”,后面的所有优化思路全要推翻。你主动确认条件,说明你读题严谨,也给自己争取了思考时间。

6.2 先给暴力解,再说优化方向

第一轮先讲哈希表计数,并说明复杂度 O(N) 时间、O(N) 空间。面试官通常会追问“可以优化空间吗”,这时候你可以提随机化思路,顺带指出它的缺点,然后引出鸽巢原理。如果你一上来就写窗口解法,面试官反而可能怀疑你是不是背题。先给暴力解,再给最优解,展示的是你的思考路径,比直接甩最优解可信得多。

6.3 写代码时讲出关键证明

写窗口解法的过程中,我会边写边讲:“这里我检查距离为 1 和 2 的位置对,因为如果所有重复元素间距都大于等于 3,N 个重复元素至少需要 3N-2 个位置,当 N>=3 时超出数组长度 2N,矛盾。N=2 的特殊情况由首尾比较兜底。”这段话不一定要完整背下来,但至少要让面试官感觉到,你对“为什么窗口大小是 3”有严格论证,而不是碰运气。

6.4 主动补充边界测试用例

写完代码后,主动给出测试用例,比如[1, 2, 3, 1]验证首尾分支,[1, 2, 1, 3]验证距离 2 分支,[1, 1, 2, 3]验证相邻分支。你可以说:“这三个用例覆盖了三种命中情况,分别对应相邻、隔一位、首尾。”然后口头模拟一遍执行过程,确认返回结果。这个习惯在面试里非常加分,因为很多候选人写完代码就直接说“没问题”,实际上自己都没跑过。

6.5 复杂度讨论和延伸提问的应答思路

面试官可能会问:“如果数组特别大,比如 10 亿个元素但只有一个重复元素占一半,你的算法还能跑吗?”答案是可以的,因为时间 O(N),空间 O(1),而且一次顺序遍历就能完成,不需要额外存储。你甚至可以提一句“这个算法是数据流友好的”,因为它在遍历过程中只需要记住当前位置和前后几个位置的值,不需要回溯很远的距离。这在面试官眼里是加分项,说明你有工程直觉。

还有可能的追问是:“如果重复元素不是 N 次,而是大于 N/2,但其他元素也可能重复,这题还能用窗口解吗?”答案是不能。因为“其他元素也可能重复”破坏了“找到两个相同值就一定是目标元素”的等价性。此时应该回到摩尔投票或哈希表。你可以预判这个追问,主动说明这个解法的适用边界,会显得你理解得很透彻。

7. 刷题之后的个人体会:别急着刷量,先把一道题榨干

写到这里,我想说点题外话。很多人刷题喜欢一天刷十几道,每道题看一眼题解、抄一遍代码就算过了。但这道 2N 数组题目,如果只是背下窗口解法的代码,那你收获的东西非常有限。真正值得花时间的,是那个“为什么窗口大小是 3”的推导过程,以及“距离不超过 2 必然存在”这个结论的证明思路。把这个思路吃透,比多做十道简单题更有用。

我在实际刷题过程中发现,把一道题从暴力解到最优解、从实现到证明、从单一题目到同类题目迁移,整个过程大概需要四十分钟到一小时,但带来的思维提升远超囫囵吞枣地刷十道题。尤其是这种“从数学约束直接推出算法结构”的题目,正是面试官最喜欢考察的核心能力,也是很多人在笔试中栽跟头的原因。如果你正在准备面试,建议把这道题作为“如何分析题目约束”的经典案例,反复琢磨,直到你能不看题解独立推导出窗口大小为 3 的解法,那就算真正掌握了。

顺带分享一个小技巧:我在本地做题时,会给窗口解法加一段验证代码,随机生成大量满足条件的测试数组,然后用哈希表解法做基准,对比两种解法结果是否一致。这样跑几百组随机数据,我就能对解法的正确性非常放心。等你日后遇到类似的“数学推导型”题目,也可以用这套“随机生成 + 暴力验证”的方法来检验自己的解法是否正确——它能帮你发现很多手推证明时容易忽略的边界情况。

相关新闻

直播产品4个供给策略:数据跟卖、反向定制、产地直供、场景节点

直播产品4个供给策略:数据跟卖、反向定制、产地直供、场景节点

做直播运营这几年&#xff0c;我越来越认定一句话&#xff1a;直播间能走多远&#xff0c;七分在选品&#xff0c;三分在主播。流量可以投&#xff0c;话术可以磨&#xff0c;场景可以换&#xff0c;唯独产品的供给策略一旦定错&#xff0c;后面所有的努力都是在给错误方向加杠…

2026/10/20 20:25:08 阅读更多 →
IMX6ULL ADC驱动开发实战:从设备树到IIO子系统全解析

IMX6ULL ADC驱动开发实战:从设备树到IIO子系统全解析

很多做嵌入式Linux的朋友第一次碰到IMX6ULL的ADC驱动开发&#xff0c;第一反应都是&#xff1a;这不就是个简单的寄存器操作&#xff1f;我在单片机上读个电位器那是家常便饭。可真到把设备树、引脚复用、IIO子系统、时钟管理全部串起来的时候&#xff0c;才发现这是个标准的&q…

2026/10/20 20:25:08 阅读更多 →
AI助力继续教育论文:千笔AI写作与万方智搜AI搭配指南

AI助力继续教育论文:千笔AI写作与万方智搜AI搭配指南

2026年的继续教育申报季又到了&#xff0c;身边不少同事已经开始为职称论文发愁。白天在医院、学校、工地、财务科忙了一整天&#xff0c;晚上还要打开电脑憋一篇继续教育论文——申报职称、修学分、结业考核&#xff0c;哪一样都躲不开。对着屏幕半小时敲不出两行字&#xff0…

2026/10/20 20:25:08 阅读更多 →

最新新闻

AI编程项目如何避免90天腐烂?构建可验证迭代闭环

AI编程项目如何避免90天腐烂?构建可验证迭代闭环

1. 这不是危言耸听&#xff1a;AI编程项目“烂掉”的真实时间线“你的 AI 编程项目迟早会烂掉&#xff0c;除非你做了这件事”——这句话刚在某技术社区刷屏时&#xff0c;我正盯着一个上线才三个月的智能代码补全插件发呆。它最初能准确预测87%的函数调用&#xff0c;现在连基…

2026/10/20 21:01:55 阅读更多 →
云真机CLI化:用adb命令无缝接入云端设备实现自动化测试

云真机CLI化:用adb命令无缝接入云端设备实现自动化测试

1. 从"云真机只能网页点点点"到CLI接管&#xff1a;这个项目要解决的问题做Android开发或者测试的同行应该都有这种经历&#xff1a;手头的真机就那几台&#xff0c;系统版本覆盖不全&#xff0c;异构屏占比机型更是一机难求。遇到需要验证老版本兼容性、多机型回归、…

2026/10/20 21:01:55 阅读更多 →
C++学习之命名空间详解

C++学习之命名空间详解

前言 命名空间&#xff08;namespace&#xff09;解决的是「名字冲突」这个古老问题。两个库都提供了 init()&#xff0c;两个头文件都定义了 Node&#xff0c;如果没有命名空间&#xff0c;把它们放进同一个程序就会撞名。C 的做法是把名字放进不同的作用域容器&#xff0c;用…

2026/10/20 21:01:55 阅读更多 →
Hadoop开发环境搭建实战:Eclipse集成与WordCount调试

Hadoop开发环境搭建实战:Eclipse集成与WordCount调试

1. 为什么还要回头啃 Hadoop 环境搭建这块硬骨头大数据入门这条路上&#xff0c;有个特别有意思的现象&#xff1a;很多人能跟你聊明白 MapReduce 的 Shuffle 过程&#xff0c;能画得出 HDFS 的架构图&#xff0c;但真让他从零把一套本地开发环境跑起来&#xff0c;立马卡壳。我…

2026/10/20 21:01:55 阅读更多 →
Hadoop入门实战:从伪分布式环境搭建到WordCount完整跑通

Hadoop入门实战:从伪分布式环境搭建到WordCount完整跑通

1. 为什么我要重新认真梳理Hadoop入门这件事大数据这个词现在几乎成了每个技术团队的标配&#xff0c;不管你是做后端、做数据分析还是做运维&#xff0c;只要公司数据量一上来&#xff0c;Hadoop这三个字就绕不开。但说实话&#xff0c;我见过太多人卡在入门这一步——不是被M…

2026/10/20 21:01:55 阅读更多 →
多模态情绪识别与情感计算在心理咨询中的落地实践

多模态情绪识别与情感计算在心理咨询中的落地实践

我最早接触情感计算在心理咨询里的应用&#xff0c;是陪着一位做咨询师督导的朋友复盘一段录像。录像里&#xff0c;来访者全程语气平稳、表情克制&#xff0c;嘴里反复说着“都还好、能应付”。朋友凭二十年的临床经验判断这人状态不对&#xff0c;但被问到具体哪里不对&#…

2026/10/20 21:00:51 阅读更多 →

日新闻

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

CLRC663芯片Pin-to-Pin替代实战:从评估到量产全记录

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

2026/10/20 1:01:30 阅读更多 →
ESP32舵机控制指南:原理、多路驱动与避坑实战

ESP32舵机控制指南:原理、多路驱动与避坑实战

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

2026/10/20 1:01:30 阅读更多 →
位移法实现一阶低通滤波:单片机ADC采样降噪实战

位移法实现一阶低通滤波:单片机ADC采样降噪实战

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

2026/10/20 1:01:30 阅读更多 →

周新闻

基于GMM的语音识别系统原理与MATLAB实现详解

基于GMM的语音识别系统原理与MATLAB实现详解

说到基于高斯混合模型&#xff08;GMM&#xff09;的语音识别系统&#xff0c;很多人第一反应就是一堆数学公式&#xff0c;觉得门槛很高。但如果你真的在MATLAB里把它落地跑通&#xff0c;会发现核心其实就是三件事&#xff1a;把声音变成特征向量&#xff0c;用GMM给每个类别…

2026/10/20 10:02:13 阅读更多 →
数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理入门:反白、灰度化、调色板修改与导出txt实践

数字图像处理这四个字听起来挺唬人&#xff0c;但落到具体操作上&#xff0c;无非就是让计算机按你的想法“摆弄”像素。我在一个模拟项目X里用了一套特别经典的基础处理流程&#xff1a;对图像做反白、改调色板、彩色转灰度&#xff0c;最后把像素数据导出成txt文件。单看每一…

2026/10/20 20:43:59 阅读更多 →
机器人大脑技术盘点:具身智能的感知、决策与数据闭环

机器人大脑技术盘点:具身智能的感知、决策与数据闭环

如果你的信息流最近也天天刷到“机器人大脑”这个词&#xff0c;你会发现一个很有意思的现象&#xff1a;每个人说的“大脑”根本不是同一个东西。有人指的是大语言模型&#xff0c;有人指的是运动控制系统&#xff0c;还有人指的是能理解物理世界的端到端模型。在2026年这个时…

2026/10/20 2:50:50 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

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

2026/10/20 10:02:08 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

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

2026/10/20 9:50:17 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

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

2026/10/20 9:50:45 阅读更多 →