正确做法是先用std::map统计字符频次,再将键值对存入vector,用sort配合lambda按second(频次)排序;降序时return a.second > b.second,相同频次需加二级比较a.first < b.first以保证ASCII升序。

用 std::map 统计频次再按值排序
直接对字符串字符按出现频率升序或降序排列,核心是先统计每个字符的出现次数,再以频次为 key 重新组织顺序。C++ 没有内置“按 value 排序 map”的操作,所以不能只靠 std::map —— 它默认按 key(字符)排序,不是按频次。
常见错误是写成:std::map<char int> freq; for (char c : s) freq[c]++;</char> 然后直接遍历 freq,结果得到的是按 ASCII 码排的字符,不是按频次。
- 正确做法:把
freq的键值对拷贝进std::vector<:pair int>></:pair> - 用
std::sort配合 lambda,按second(即频次)排序,比如降序:[&](auto& a, auto& b) { return a.second > b.second; } - 注意:相同频次时,若需稳定顺序(如按原始字符 ASCII 升序),得在 lambda 中加二级比较:
a.second != b.second ? a.second > b.second : a.first
处理大小写和非字母字符要提前明确规则
输入字符串含大小写字母、空格、标点或数字时,'A' 和 'a' 默认算不同字符。很多实际需求(比如词频统计)要求忽略大小写,但 C++ 的 std::map 不会自动做转换。
容易踩的坑是直接用 std::tolower(c) 但没处理 char 的符号扩展问题 —— 在某些平台 char 是 signed,传给 std::tolower 可能触发未定义行为。
立即学习“C++免费学习笔记(深入)”;
- 安全写法:
std::tolower(static_cast<unsigned char>(c))</unsigned> - 如果只要字母参与排序,得先过滤:
if (std::isalpha(c)) { /* 统计 */ } - 空格、换行等空白字符默认计入频次,若不希望它们出现在结果里,必须显式跳过
用 std::array<int></int> 替代 map 提升性能
当确定字符集是 ASCII(0–255),且不关心 Unicode,用 std::array<int></int> 统计比 std::map 快得多 —— 避免了红黑树插入开销和内存分配。
但要注意:char 可能为负,直接当数组下标会越界。例如 char c = '\xFF'; arr[c]++ 实际访问的是 arr[-1],崩溃风险高。
- 统一转为
unsigned char下标:++count[static_cast<unsigned char>(c)];</unsigned> - 构建结果 vector 时,遍历 0–255,跳过 count[i] == 0 的项;i 就是字符本身,频次是
count[i] - 排序逻辑不变,只是数据源从
vector<pair>></pair>换成vector<tuple char int>></tuple>或类似结构
构造最终字符串时别漏掉重复字符
排序后得到的是 vector<pair int>></pair>,每个元素代表“字符 + 出现次数”。很多人只循环 push_back 每个字符一次,结果丢了重复。
典型错误示例:for (auto& p : sorted) result += p.first; —— 这只拼了每个字符一次。
- 正确做法:对每个
p,循环p.second次追加p.first - 更高效写法:
result.append(p.second, p.first);(std::string::append(size_t n, char c)) - 如果原字符串含中文或 UTF-8 多字节字符,这套方法会出错 —— 因为
char级统计会拆开字节。这种场景必须用 ICU 或手动解析 UTF-8,不是简单改容器能解决的
字符频次排序真正麻烦的不是排序本身,而是边界情况:符号扩展、空字符处理、多字节编码、相同频次的保序需求。这些地方一不留神,测试用例跑过,线上遇到特殊输入就崩。


















