结论:std::sort+std::unique仅适用于不关心顺序且可修改原串的去重;保序去重要用unordered_set遍历;std::unique不删元素,须配合erase截断,且只去相邻重复,故需先排序。

直接说结论:用 std::sort + std::unique 去重字符串,只适用于「不关心原顺序」且「允许修改原字符串」的场景;若需保持首次出现顺序或处理 std::string 中重复字符(非子串),必须先转为容器再操作,不能对 std::string 对象直接调用 std::unique 后就完事。
为什么不能对 std::string 直接 std::unique?
std::unique 不会真正删除元素,只是把重复元素“挤到末尾”,并返回一个新逻辑终点迭代器;它要求容器支持随机访问和可写,std::string 虽然满足,但常见误用是忘了用 erase 截断尾部冗余——结果看起来没变化。
- 错误写法:
std::unique(s.begin(), s.end())单独调用 → 字符串长度不变,末尾残留脏数据 - 正确组合:
s.erase(std::unique(s.begin(), s.end()), s.end()) - 注意:
std::unique只移除「相邻重复项」,所以必须先std::sort—— 但排序会彻底打乱原始字符顺序
std::sort + std::unique 的典型用法(去重并排序)
这是最省事的方案,适合生成唯一字符集、做集合交并、或后续只需遍历不关心顺序的场景。
std::string s = "hello world"; std::sort(s.begin(), s.end()); // 变成 " dehllloorw" s.erase(std::unique(s.begin(), s.end()), s.end()); // 变成 " dehlorw"
-
std::sort默认按 ASCII 码升序,空格(32)会排在字母前 - 若想忽略大小写,得传自定义比较器:
[](char a, char b) { return std::tolower(a) - 性能上,
std::sort是 O(n log n),std::unique是 O(n),整体瓶颈在排序
如何保持原始字符顺序去重?
这时候 std::sort 就不能用了。得换思路:遍历 + 记录已见字符,用 std::unordered_set 加速查找。
std::string s = "hello world";
std::unordered_set<char> seen;
std::string result;
for (char c : s) {
if (seen.find(c) == seen.end()) {
seen.insert(c);
result += c;
}
}
// result == "helo wrd"
</char>
- 时间复杂度 O(n),空间 O(k),k 是不同字符数(ASCII 下最多 256)
- 注意:
std::unordered_set构造/查找有常数开销,小字符串(如 len std::vector(256) 位图更快 - 如果输入含 Unicode(UTF-8 多字节),这套按
char处理会出错——此时应改用std::u32string或第三方库解析码点
容易被忽略的边界问题
实际写业务代码时,这几个点常导致线上 bug:
-
std::unique对空字符串或单字符字符串安全,但s.begin() == s.end()时调用std::sort没问题,别加多余判空 - 若字符串含嵌入空字符(
'\0'),std::string仍能正常存取,但用c_str()传给 C 函数会截断——去重后务必确认是否影响下游 - 多线程环境下,不要对同一
std::string并发调用sort+unique,没有内部同步 - 用
std::vector<char></char>替代std::string也能跑通,但语义上不如std::string清晰;除非你明确需要 reserve/resize 控制内存
真正麻烦的从来不是调用两个函数,而是想清楚:你要的是集合去重、有序去重,还是保序去重——选错了前提,后面所有优化都是白搭。
C++免费学习笔记(深入):立即使用
在学习笔记中,你将探索 C++ 的入门与实战技巧!











