用std::unordered_set边遍历边查重是最直接的方法:从左到右扫描,遇重复元素立即返回,确保得到第一个重复出现的值,时间o(n)、空间o(n)。

用 std::unordered_set 边遍历边查重是最直接的方法
核心思路是:从左到右扫描数组,每遇到一个元素就检查它是否已存在;若已存在,立即返回——这保证拿到的是「第一个重复出现」的值(不是第一次出现的位置重复,而是该值第二次出现时最早的那个)。std::unordered_set 平均 O(1) 查找,整体时间复杂度 O(n),空间 O(n)。
常见错误是误用 std::set(红黑树,O(log n) 插入/查找),或先排序再扫(会破坏原始顺序,找不到“第一个”)。
示例代码片段:
int findFirstDuplicate(const std::vector<int>& arr) {
std::unordered_set<int> seen;
for (int x : arr) {
if (seen.find(x) != seen.end()) {
return x; // 第一个被发现重复的元素
}
seen.insert(x);
}
return -1; // 无重复
}</int></int>
数组元素范围小且为非负整数时,可用原地哈希技巧省空间
当所有元素都在 [0, n-1] 范围内(n 是数组长度),可把数组本身当哈希表用:遍历中,将每个值 x 视为下标,把 arr[x] 标记为负数。若某次访问 arr[abs(x)] 时发现已是负数,说明 x 已出现过——这就是第一个重复元素。
注意点:
- 必须确保所有元素 ≥ 0,否则取
abs()会出错 - 原数组会被修改,不可用于只读场景
- 标记方式要统一(比如全变负,不能混用 0 或特殊值)
- 需跳过 0 值的边界处理(若 0 允许出现,得额外判断)
用 std::map 记录首次出现位置也能解,但没必要
有人会想到:先扫一遍存每个元素的首次索引,再扫一遍查哪个重复元素的「第二次出现位置」最小。这逻辑正确,但多遍扫描 + 更高常数开销,不如单次遍历 + unordered_set 直观高效。
真正需要 std::map 的场景是:你要返回的是「重复元素第一次出现的下标」,而非元素值本身。此时必须存位置,不能只存是否存在。
性能上:unordered_map 和 unordered_set 在此题中没有本质区别,但后者语义更清晰、内存略省。
LeetCode 类题目常考边界:空数组、全不重复、重复在末尾
实操时容易漏掉这些情况,导致运行时错误或逻辑错:
- 空数组或单元素数组 → 直接返回哨兵值(如 -1),别忘了判空
- 全不重复 → 确保循环外有明确返回,避免未定义行为
- 重复元素在最后两个位置(如
[1,2,3,4,5,5])→ 验证你的逻辑是否真能捕获它(有些手写哈希误判越界) - 元素类型是
long long或自定义结构体 →unordered_set需提供哈希函数和等价比较,不能直接用
最易被忽略的是:题目说「第一个重复出现的元素」,指的是该元素第二次出现的那个时刻最早,不是它第一次出现的位置最小。这个语义偏差会导致整个思路跑偏。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











