
本文介绍多种时间复杂度更优的方案(如 o(n) 哈希表法、一行式函数式写法),替代原始 o(n²) 双重循环,精准满足“返回第二个出现位置最靠前的重复数字”这一核心要求。
本文介绍多种时间复杂度更优的方案(如 o(n) 哈希表法、一行式函数式写法),替代原始 o(n²) 双重循环,精准满足“返回第二个出现位置最靠前的重复数字”这一核心要求。
在解决“查找数组中首个重复数字”问题时,关键在于准确理解题意:不是找第一个被重复的数字(即第一次出现就重复的数),而是找所有重复数字中,“第二次出现位置索引最小”的那个数。例如 [2, 1, 3, 5, 3, 2] 中,3 的第二次出现在索引 4,2 的第二次出现在索引 5,因此应返回 3 —— 这决定了最优解必须按顺序遍历,边记录边判断,而非暴力枚举所有配对。
✅ 推荐解法:单次遍历 + Set(时间 O(n),空间 O(n))
使用 Set 记录已见过的元素,首次遇到已在集合中的元素,即为答案:
function findFirstRepeated(gifts) {
const seen = new Set();
for (const gift of gifts) {
if (seen.has(gift)) {
return gift;
}
seen.add(gift);
}
return -1;
}
该解法逻辑清晰、性能稳定,且完全符合题目语义:只要某数第二次出现,立刻返回,此时其第二次索引必然最小(因我们是从左到右扫描,第一个触发 seen.has() 为 true 的时刻,就是所有重复数中“第二次出现位置最早”的时刻)。
⚡ 进阶写法:一行式函数式风格(ES6+)
利用闭包与 Array.prototype.find() 实现简洁表达(注意:可读性略低,适合熟悉高阶函数者):
const findFirstRepeated = gifts => gifts.find((seen => x => seen.has(x) || !!seen.add(x))(new Set())) ?? -1;
? 解析:
(seen => x => ...)(new Set())是立即执行函数,创建独立Set闭包;seen.has(x)判断是否重复,!!seen.add(x)确保添加并返回布尔值(add()返回Set自身,!!转为true,使逻辑或短路生效)。?? -1处理无重复情况。
❌ 原始方法的问题
你提供的双重循环实现虽正确,但时间复杂度为 O(n²),且逻辑稍显冗余:
- 内层循环从
i+1开始逐个比对,实际只需知道“当前元素此前是否出现过”; -
indexOfDup和duplicated的维护增加了理解成本,也易引发边界错误。
✅ 注意事项与验证
- ✅ 该问题不区分
===与==,但建议统一用严格相等(===),避免类型隐式转换陷阱(如0 == false); - ✅ 输入为空数组
[]或单元素数组时,应正确返回-1; - ✅ 测试用例验证:
console.log(findFirstRepeated([2, 1, 3, 5, 3, 2])); // 3 console.log(findFirstRepeated([1, 2, 3, 4])); // -1 console.log(findFirstRepeated([5, 1, 5, 1])); // 5(5 第二次出现在索引 2,早于 1 的索引 3)
综上,单次遍历 + Set 是兼顾效率、可读性与健壮性的首选方案。它将时间复杂度从 O(n²) 降至 O(n),代码行数减少 60%,同时语义直观、易于测试与维护。










