最常用做法是用 std::unordered_set 边遍历边记录,首次遇到已存在元素即返回该值;时间复杂度 o(n),空间复杂度 o(n),适用于任意整数(含负数、边界值),需注意空输入处理和返回值语义明确。

用 std::unordered_set 实现 O(n) 查找
直接遍历数组,边走边记见过的数,第一次遇到已存在的数就返回——这是最常用也最稳妥的做法。关键不是“找重复”,而是“找**第一对**”,即索引最小的重复元素对(比如 [1,2,3,2,4,3] 中,2 在索引 1 和 3 处重复,比 3 更早成对,所以答案是 2)。
实操建议:
-
std::unordered_set插入和查找平均 O(1),总时间复杂度 O(n),空间 O(n) - 别用
std::set(红黑树),它带 log 开销,没必要 - 如果数组元素范围小且已知(如 0~999),可用
std::vector<bool></bool>或原始 bool 数组替代,更快更省内存 - 注意:只检查“是否见过”,不存索引——因为我们要的是“第一个重复出现的值”,不是“哪两个位置”
int findFirstDuplicate(const std::vector<int>& nums) {
std::unordered_set<int> seen;
for (int x : nums) {
if (seen.find(x) != seen.end()) return x;
seen.insert(x);
}
return -1; // 无重复
}
</int></int>
为什么不能用两层 for 循环暴力扫?
能跑通,但容易在面试或线上代码里被质疑——尤其是数据量稍大时(比如 10⁵ 元素),O(n²) 会超时或拖慢服务。
常见错误现象:
- 写成“找所有重复再取第一个”,逻辑绕远,还多遍历一遍
- 内层循环从
i+1开始,却漏判i==0时的边界情况(其实不会错,但容易写混) - 返回的是下标对而非数值,和题目要求不符
如果真要用暴力法(比如调试验证),至少保证:
组合式C++代码评审方案,融合静态分析、AI推理、多轮迭代评审和C++专项检查,适用于PR审查、增量代码审查、全项目评审和代码质量评分,触发词包括review cpp、cpp代码评审、C++review、代码审查。
- 外层
i从 0 到n-2,内层j从i+1到n-1 - 一旦
nums[i] == nums[j]就立刻return nums[i],不要继续
整数溢出或负数会影响 unordered_set 吗?
完全不影响。std::unordered_set<int></int> 对负数、零、INT_MAX/INT_MIN 都正常处理,哈希函数本身支持全范围 int。
但要注意两个实际坑点:
- 如果数组含大量相同数(比如全是
0),unordered_set插入第一次后,第二次就命中,没问题;但极端情况下哈希冲突变多,性能略降——不过对“第一对”问题,最多只插入一个重复值前的所有数,影响微乎其微 - 别误用
std::unordered_map存频次再扫一遍:多一次遍历 + 多存 key-value,纯属冗余 - 若元素类型是
long long或自定义结构体,必须提供哈希特化(std::hash重载),但题目明确是“数字”,默认int就够
返回值设计:没重复时该返回什么?
没有统一标准,取决于调用上下文。常见做法有三种:
- 返回特殊哨兵值,如
-1(前提是数组元素非负) - 返回
std::optional<int></int>(C++17+),最语义清晰:if (auto res = findFirstDuplicate(nums)) { ... } - 抛异常(不推荐),因为“无重复”不是异常场景,是合法输入
多数工程代码倾向 std::optional;刷题平台常用 -1 或 0。选哪个不重要,重要的是函数签名要明确传达“可能无结果”。
最容易被忽略的一点:题目说“第一对重复的数字”,隐含前提是“存在重复”。但真实数据总有意外,空数组、全不同、甚至未初始化内存都可能触发未定义行为——加个空检查和断言,比靠运气强。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!










