
本文介绍多种优化方案,从时间复杂度 o(n²) 的嵌套循环升级到 o(n) 的单次遍历解法,重点讲解基于 set 的一行式实现及其原理,并提供可读性强、鲁棒性高的生产级代码。
本文介绍多种优化方案,从时间复杂度 o(n²) 的嵌套循环升级到 o(n) 的单次遍历解法,重点讲解基于 set 的一行式实现及其原理,并提供可读性强、鲁棒性高的生产级代码。
在处理“查找首个重复数字”这类问题时,核心要求是:返回第一个「第二次出现位置最靠前」的数字(即其第二处索引最小)。例如 [2, 1, 3, 5, 3, 2] 中,3 第二次出现在索引 4,而 2 第二次出现在索引 5,因此答案是 3。
你当前的双层 for 循环实现虽然逻辑正确,但时间复杂度为 O(n²),在大数据量下性能较差。更优解法应利用哈希结构(如 Set 或 Map)实现 O(n) 时间 + O(n) 空间 的单次遍历。
✅ 推荐解法:Set 辅助单次遍历(清晰易懂版)
function findFirstRepeated(gifts) {
const seen = new Set();
for (const num of gifts) {
if (seen.has(num)) return num;
seen.add(num);
}
return -1;
}
该解法逻辑简洁:遍历数组,对每个元素检查是否已在 Set 中存在;若存在,说明这是它第二次出现,且因我们按顺序遍历,首次命中即对应最小的第二次索引——完全符合题目语义。无需额外记录索引或比较位置。
⚡ 进阶技巧:一行式函数式写法(含闭包与空值合并)
const findFirstRepeated = gifts => gifts.find((seen => x => seen.has(x) || seen.add(x) && false)(new Set())) ?? -1;
⚠️ 注意:此写法虽精炼,但可读性较低。其关键在于:
- 使用立即执行函数
(seen => ...)(new Set())创建闭包,隔离Set实例; -
seen.has(x)为true时直接返回x(即首次重复值); - 否则执行
seen.add(x) && false:add()返回undefined(falsy),&& false确保整个表达式返回false,使find()继续迭代; -
?? -1处理无重复时返回-1。
? 关键注意事项
-
不要用
indexOf/lastIndexOf判断重复:它们每次调用都是 O(n),整体仍退化为 O(n²); -
避免使用
includes()替代has():Array.includes()是 O(n),而Set.has()是 O(1); -
==应改为===:原代码中gifts[i] == gifts[j]存在隐式类型转换风险,建议统一用严格相等; - 边界兼容性:本解法天然支持空数组、单元素、全相同等边界情况,无需额外判断。
✅ 最终推荐(兼顾性能、可读与健壮性)
function findFirstRepeated(gifts) {
if (!Array.isArray(gifts) || gifts.length === 0) return -1;
const seen = new Set();
for (const gift of gifts) {
if (seen.has(gift)) return gift;
seen.add(gift);
}
return -1;
}
该版本添加了输入校验,语义清晰,时间复杂度稳定为 O(n),空间复杂度 O(k)(k 为首次重复前不同元素个数),是 Advent.js Day 1 挑战的理想解决方案。










