std::unique配合erase是最直接的去重方法,但要求容器已排序且支持随机访问迭代器;它仅移除相邻重复项并返回新逻辑尾迭代器,需erase才能真正缩容。

用 std::unique 配合 erase 是最直接的做法
前提是数组已排序(升序或降序),且你用的是 std::vector 或其他支持随机访问迭代器的容器。注意:std::unique 不会真正删除元素,只是把重复项移到末尾,并返回去重后新逻辑区间的尾后迭代器。
实操建议:
- 必须配合
erase才能真正缩容,否则容器大小不变,只是数据布局变了 -
std::unique默认用==比较,对自定义类型需传入二元谓词(比如[](const auto& a, const auto& b) { return a.id == b.id; }) - 它只移除「相邻重复项」,所以必须先排序;若原数组无序,先
std::sort再unique,但此时时间复杂度升为 O(n log n)
示例:
std::vector<int> nums = {1, 1, 2, 2, 2, 3};
auto last = std::unique(nums.begin(), nums.end());
nums.erase(last, nums.end()); // nums 变为 {1, 2, 3}
</int>
手动双指针实现(适合原地修改、不依赖 STL 的场景)
当输入是裸指针数组(如 int* arr)或需要严格控制内存、避免额外分配时,手写双指针更可控。核心思路:用一个慢指针 slow 指向当前唯一值应存放位置,快指针 fast 遍历全部元素,跳过与前一元素相同的项。
常见错误现象:
- 越界访问:
fast从 1 开始,但没检查fast ,尤其 <code>n == 0或n == 1时容易崩 - 误判重复:比较
arr[fast] == arr[fast - 1]是对的,但写成== arr[slow]就错——因为slow指向的是下一个待填位置,不是上一个已存值的位置 - 忽略返回长度:函数通常需返回新长度,不能只改内容不告诉调用方“有效部分到哪”
示例(返回去重后长度):
int removeDuplicates(int* nums, int n) {
if (n == 0) return 0;
int slow = 1;
for (int fast = 1; fast <h3>使用 <code>std::set</code> 或 <code>std::unordered_set</code>?不推荐</h3><p>虽然能去重,但完全破坏了「有序数组」的前提优势:一是失去 O(1) 空间复杂度(<code>set</code> 额外 O(n) 空间 + O(n log n) 时间),二是结果不保证顺序(<code>unordered_set</code> 无序,<code>set</code> 虽有序但重建后仍是新容器,无法原地操作)。</p><p>适用场景仅限:你根本不在乎原数组、也不在乎性能,只想快速拿到一个无重集合。但题目明确说「在有序数组中删除」,意味着原地、保序、高效是隐含要求。</p><p>性能影响:</p>
-
std::set插入单个元素 O(log n),总 O(n log n),比双指针 O(n) 慢且内存开销大 - 所有基于哈希或红黑树的方案都无法复用「已排序」这个信息,属于杀鸡用牛刀
边界情况和易忽略点
实际编码中最容易漏掉的不是算法逻辑,而是这些细节:
-
n == 0或n == 1时是否直接返回,避免进入循环导致越界或逻辑错乱 - 如果数组元素是浮点数,用
==判断相等可能失效,需改用误差范围比较(但此时「重复」定义本身需重新审视) - 若用
std::unique处理std::array,因std::array大小固定,无法erase,只能靠返回的迭代器知道有效长度,后续必须手动记下这个长度来使用 - 多线程环境下,若数组被其他线程读写,
unique + erase不是原子操作,需加锁
真正的难点不在“怎么删”,而在“删完之后怎么让别人知道删成了什么样”——返回长度、维护迭代器有效性、兼容不同容器语义,这些比算法本身更常出问题。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











