首选 std::unordered_set 辅助去重以获得无重复副本,时间复杂度低且代码简洁;原地去重则用双指针法,O(n) 时间、O(1) 空间,需手动 resize。

用 std::set 或 std::unordered_set 辅助去重(适合原始数组不可变)
如果只是需要一份无重复的副本,且不关心原数组顺序是否保留,最稳妥的做法是借助标准容器自动去重。注意:std::set 会排序,std::unordered_set 保持插入顺序但需类型支持哈希。
- 对
int数组,std::unordered_set是首选:插入快、查重快、不改变逻辑顺序 - 若原数组很大,反复调用
insert()后再转回 vector,比手写双指针更易读、不易出错 - 不能直接用于 C 风格数组(如
int arr[10]),必须先知道长度,或改用std::vector
std::vectornums = {1, 2, 2, 3, 3, 4}; std::unordered_set seen; std::vector unique; for (int x : nums) { if (seen.insert(x).second) { // insert 返回 pair ,second 为 true 表示新插入 unique.push_back(x); } }
原地去重:用双指针法处理 std::vector
这是最常用也最高效的原地去重方式,时间复杂度 O(n),空间 O(1),但要求元素可比较(如 ==),且只保留首次出现的元素。
- 适用于已排序数组时效果最佳;未排序时也能用,但结果顺序不变,重复项被“挤掉”,末尾留垃圾值
- 返回的是新逻辑长度,必须手动调用
resize()才真正缩短容器 - 别直接对裸数组(如
int arr[10])用双指针——没 size 信息,容易越界
std::vectorv = {1, 2, 2, 3, 2, 4}; if (v.empty()) return; size_t i = 0; for (size_t j = 1; j < v.size(); ++j) { if (v[j] != v[i]) { v[++i] = v[j]; } } v.resize(i + 1); // 关键:不 resize 就只是前 i+1 个有效
用 std::unique + erase 组合(仅适用于已排序数组)
std::unique 不是真的删除元素,它把重复项移到末尾并返回新尾迭代器。必须配合 erase 才算完成去重,而且只对相邻重复有效 —— 换句话说,数组必须先排序。
- 误用场景:对未排序数组直接调
unique,只会删掉“连着出现”的重复,比如{1,2,1}变成{1,2,1}(没变),因为两个1不相邻 - 排序成本不可忽略:
O(n log n),比双指针 O(n) 慢,除非你本来就要排序 - 对
std::vector安全;对裸数组要用std::unique(arr, arr + n),小心传错长度
std::vectorv = {3, 1, 2, 1, 3, 2}; std::sort(v.begin(), v.end()); // 必须先排 auto last = std::unique(v.begin(), v.end()); v.erase(last, v.end());
处理 C 风格数组时的常见陷阱
很多人卡在“怎么对 int arr[10] 去重”,根本问题是:C 数组没有长度元信息,函数无法知道边界,传参必须显式带长度,且无法安全缩容。
立即学习“C++免费学习笔记(深入)”;
- 不能用
sizeof(arr)/sizeof(*arr)在函数内求长度 —— 数组退化为指针后该表达式失效 - 所谓“删除”只能是:把唯一元素前移,返回新有效长度,调用方负责用该长度访问
- 如果真要缩容,必须换用
std::vector或动态分配新内存 +memcpy,别试图realloc栈数组
// 正确传参方式
int removeDuplicates(int arr[], int n) {
if (n == 0) return 0;
int i = 0;
for (int j = 1; j < n; ++j) {
if (arr[j] != arr[i]) {
arr[++i] = arr[j];
}
}
return i + 1; // 新长度
}
// 调用方需记录返回值:int new_len = removeDuplicates(arr, 10);
裸数组去重没有银弹,关键在分清“要不要改原数组”“数组是否已排序”“能不能换容器”。用错 std::unique 或忽略 resize 是高频失误点。











