原地去重必须用双指针,禁用erase;空数组需提前判;write_idx初始化为0并配合unordered_set查重,最后resize(write_idx);有序数组可用相邻比较双指针优化至o(1)空间。

原地去重必须用双指针,别碰 erase
直接在 std::vector 上循环调用 erase 是最常见也最坑的写法——每次 erase 都要搬移后面所有元素,时间复杂度退化成 O(n²),数组稍大就卡死。尤其当重复率高时(比如全是相同数字),性能断崖式下跌。
真正安全的原地去重,只靠两个索引:write_idx 记录下一个有效元素该放哪,i 正常遍历。两者互不干扰,避免覆盖未检查项。
- 空数组必须提前判,否则
seen.insert(arr[0])会越界 -
write_idx初始化为0,但第一次插入前不能直接赋值,得先确认元素没被见过 - 循环结束后一定要调用
arr.resize(write_idx),否则尾部残留旧数据
std::unordered_set 是唯一靠谱的查重辅助
想 O(1) 平均查找、又不破坏顺序,只能靠 std::unordered_set。它不排序,只管“有没有见过”,且对 int、double、std::string 等内置/标准类型开箱即用。
别用 std::set:它强制排序,顺序全乱;也别手写哈希表——没必要,std::unordered_set::insert() 返回 std::pair<iterator bool></iterator>,那个 bool 就是“是否首次插入”,比先 find 再 insert 少一次哈希计算。
- 自定义类型要用
std::unordered_set,必须提供std::hash<t></t>特化和operator== - 对
std::string或大对象,插入时建议用seen.insert(std::move(x))避免深拷贝 - 如果确定去重后长度远小于原长,可提前
seen.reserve(估计容量)减少 rehash
有序数组有更快解法,但仅限“已排序”前提
如果输入数组本身是**非严格递增**的(比如 [1,1,2,2,3]),完全不用哈希表——直接双指针比对相邻值,空间压到 O(1),连 std::unordered_set 都省了。
关键点是:慢指针 slow 指向当前唯一序列末尾,快指针 fast 扫描找第一个不同于 nums[slow] 的数,找到就 nums[++slow] = nums[fast]。全程无哈希、无额外容器。
- 这个方法对无序数组完全无效——
nums[fast] != nums[slow]不代表它是新元素 - 返回值是
slow + 1,不是vector.size(),别漏了加一 - 仍需最后调用
resize(slow + 1)截断,不然尾部脏数据还在
字符串或大对象去重要注意移动语义
std::vector<:string></:string> 去重时,反复 push_back 或赋值可能触发多次内存分配,尤其字符串很长。用 result.reserve(arr.size()) 预留空间能避免抖动;若知道去重后大概长度,预留更小值更省。
原地版本里,arr[write_idx++] = arr[i] 对 std::string 默认是拷贝赋值。改成 arr[write_idx++] = std::move(arr[i]) 可转为移动,避免复制底层字符缓冲区。
但注意:std::move 后 arr[i] 进入有效但未定义状态,不能再读——好在这儿你本来就不会再访问它。
真正容易被忽略的是:所有方案都依赖“元素可比较 + 可哈希”这个隐含前提,而自定义类一旦没正确定义 operator== 或哈希特化,运行时不会报错,只会静默失效。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











