最常用方案是用std::unordered_set记录已见字符并正向遍历构建新字符串,时间复杂度O(n),空间O(k);若需保留最后一个重复字符,推荐反向遍历标记再正向收集。

用 std::unordered_set 去重并保持顺序
直接遍历字符串,用 std::unordered_set 记录已见字符,边查边构建新字符串——这是最常用且兼顾效率与顺序的方案。
- 适用于 ASCII 或 UTF-8 编码下的单字节字符;若含多字节 UTF-8 字符(如中文),需先按 UTF-8 码点切分,不能直接对
char操作 -
std::unordered_set<char></char>查找平均 O(1),整体时间复杂度 O(n),空间 O(k),k 为不同字符数 - 别用
std::set:它自动排序,会打乱原始顺序;也别用std::vector::erase原地删——反复移动内存,O(n²)
std::string removeDuplicates(const std::string& s) {
std::unordered_set<char> seen;
std::string result;
result.reserve(s.size()); // 预分配避免多次 realloc
for (char c : s) {
if (seen.find(c) == seen.end()) {
seen.insert(c);
result += c;
}
}
return result;
}
遇到重复字符要保留最后一个怎么办?
标准去重默认留第一个,但业务有时要求“留最后一个”——比如日志里取最新状态,就不能简单逆序处理再反转。
- 正向遍历仍可用
std::unordered_set,但得先扫一遍记下每个字符最后出现的位置 - 更直接的做法:反向遍历 +
std::unordered_set+std::string::insert(0, 1, c),但频繁前端插入性能差 - 推荐方案:反向遍历标记,再正向收集——用
std::vector<bool></bool>或std::unordered_set存“是否已添加”,配合std::string::rbegin()/rend()
删除重复时区分大小写吗?
默认区分。如果需求是“abcABC” → “abc”,就得统一大小写再判断,但注意:不能简单用 std::tolower 处理所有 locale,尤其非 ASCII 字符可能出错。
- 纯 ASCII 场景下可安全用
std::tolower(static_cast<unsigned char>(c)) - 若输入含非 ASCII(如 é、ñ),应使用
std::locale和std::toupper/std::tolower配合 facet,或改用 ICU 库 - 多数项目其实只需 ASCII,此时建议显式注释:
// assumes ASCII input
性能敏感场景下怎么优化?
当字符串极长(如 MB 级)或调用极频繁(如网络协议解析),std::unordered_set 的哈希开销和内存分配可能成为瓶颈。
立即学习“C++免费学习笔记(深入)”;
- 若确定字符集很小(如仅 a-z、0-9),用
std::array<bool, 256>替代std::unordered_set,查表 O(1),无内存分配 - 避免返回值拷贝:用输出参数
std::string& result,调用前 clear 并 reserve - 慎用
std::string::erase原地删——内部 memmove 开销大,不如重建字符串
真正难的不是“怎么删”,而是明确“重复”的定义:是字节相等?Unicode 码点相等?还是忽略空格/换行后的逻辑相等?没理清这点,代码越优化越偏。


















