std::unique配合erase是最常用且安全的去重方法,但需先排序;若需保持原序,则用std::unordered_set记录已见元素实现o(n)去重。

用 std::unique 配合 erase 是最常用且安全的做法
直接在原数组(或 std::vector)上操作,不额外分配内存,但前提是元素已排序。否则 std::unique 只能去重相邻重复项,结果不可靠。
实操建议:
- 先调用
std::sort排序,再用std::unique+erase—— 适用于允许改变顺序的场景 -
std::unique返回的是新逻辑尾迭代器,必须配合容器的erase才真正删除元素,只调用unique不会缩容 - 对原始 C 风格数组(如
int arr[10])无法直接用erase,得手动计算长度或转成std::vector
std::vector<int> v = {1, 2, 2, 3, 3, 3, 4};
std::sort(v.begin(), v.end());
auto last = std::unique(v.begin(), v.end());
v.erase(last, v.end()); // 现在 v 是 {1,2,3,4}
</int>
需要保持原始顺序?用 std::unordered_set 记录已见元素
这是保持首次出现位置的通用解法,时间复杂度 O(n),空间换时间。注意:C++11 起才有 std::unordered_set,且要求元素可哈希(int、std::string 没问题,自定义类型需重载 hash 和 ==)。
常见错误现象:std::set 也能用,但它是有序的,插入 O(log n),整体变慢;还有人误用 find 在 vector 中线性查找,导致 O(n²) 性能崩塌。
- 遍历原容器,对每个元素检查是否已在
std::unordered_set中 - 不在则插入集合,同时 push 到结果容器(或原地移动)
- 不要用
std::vector::erase在遍历时删除 —— 迭代器失效,容易越界或漏删
std::vector<int> v = {2, 1, 3, 2, 4, 3};
std::unordered_set<int> seen;
std::vector<int> result;
for (int x : v) {
if (seen.find(x) == seen.end()) {
seen.insert(x);
result.push_back(x);
}
} // result 是 {2,1,3,4}
</int></int></int>
C 风格数组怎么处理?别硬刚,先转 std::vector
原始数组(如 int arr[N])没有成员函数,不能调用 erase,也没迭代器适配 std::unique 的便捷写法。强行手写双指针虽然可行,但易错、难维护,还容易越界。
- 优先转成
std::vector<int> vec(arr, arr + N)</int>,后续复用前面两种方法 - 如果必须原地操作(比如嵌入式环境限制内存),才考虑双指针:用
write_idx记录写入位置,read_idx遍历,每遇到新元素就拷贝并递增write_idx - 转
vector后记得用.data()和.size()回传给需要 C 接口的函数
性能与边界要注意这些坑
重复元素少时,unordered_set 方法更稳;元素多且允许重排,sort + unique 更省内存。但无论哪种,都得小心这几处:
-
std::unique对浮点数慎用 —— 直接比较==可能因精度问题失效,应先做近似判断再归类 - 自定义结构体去重时,
unordered_set需提供哈希函数和等价判断;sort + unique需提供严格弱序比较(operator 或 lambda) - 多线程环境下,所有容器操作都不是线程安全的,共享数据必须加锁或改用线程安全封装
真正麻烦的从来不是“怎么删”,而是“删完之后谁还在用旧长度”——尤其是把处理后的 size 当作原数组长度传给其他函数时,最容易踩空指针或越界读。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











