Go 语言:字符串排序详解
🔹 核心三步:字符串排序的标准流程
1 2 3 4 5 6 7 8 9 10 11
| str := "eat"
s := []byte(str)
sort.Slice(s, func(i, j int) bool { return s[i] < s[j] })
sortedStr := string(s)
|
🔹 为什么要转 []byte?
因为 Go 的 string 是不可变的,不能原地修改:
1 2
| str := "eat" str[0] = 'x'
|
| 类型 |
可变性 |
是否可排序 |
string |
❌ 不可变 |
❌ 不能原地排序 |
[]byte |
✅ 可变 |
✅ 可原地排序 |
💡 必须先转成 []byte(可修改),排完再转回 string。
🔹 sort.Slice 工作原理
参考链接
1 2 3
| sort.Slice(s, func(i, j int) bool { return s[i] < s[j] })
|
🔸 比较的是 ASCII 码值
byte 本质是 uint8,比较的是字符的 ASCII 码:
1 2
| 字符 'a' 'e' 't' ASCII 97 101 116
|
1 2
| 排序前: ['e', 'a', 't'] → [101, 97, 116] 排序后: ['a', 'e', 't'] → [97, 101, 116] ✅ 升序排列
|
🔸 常用比较函数写法
1 2 3 4 5 6 7 8 9 10
| sort.Slice(s, func(i, j int) bool { return s[i] < s[j] })
sort.Slice(s, func(i, j int) bool { return s[i] > s[j] })
sort.Slice(s, func(i, j int) bool { return strings.ToLower(string(s[i])) < strings.ToLower(string(s[j])) })
|
🔹 在算法中的应用:字母异位词分组
🎯 核心思想
字母异位词排序后得到相同的字符串,可以作为 map 的 key 来分组。
1 2 3 4 5 6 7 8 9
| "eat" → 排序 → "aet" ┐ "tea" → 排序 → "aet" ├→ 同一组 "ate" → 排序 → "aet" ┘
"tan" → 排序 → "ant" ┐ "nat" → 排序 → "ant" ├→ 同一组 ┘
"bat" → 排序 → "abt" → 单独一组
|
🗂️ Map 分组状态
1 2 3 4 5
| map[string][]string{ "aet": {"eat", "tea", "ate"}, "ant": {"tan", "nat"}, "abt": {"bat"}, }
|
🔹 完整流程图解
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| 输入: ["eat", "tea", "tan", "ate", "nat", "bat"]
遍历每个字符串: ┌─────────────────────────────────────────┐ │ "eat" → []byte → 排序 → "aet" │ │ mp["aet"] = ["eat"] │ ├─────────────────────────────────────────┤ │ "tea" → []byte → 排序 → "aet" │ │ mp["aet"] = ["eat", "tea"] │ ├─────────────────────────────────────────┤ │ "tan" → []byte → 排序 → "ant" │ │ mp["ant"] = ["tan"] │ ├─────────────────────────────────────────┤ │ "ate" → []byte → 排序 → "aet" │ │ mp["aet"] = ["eat", "tea", "ate"]│ ├─────────────────────────────────────────┤ │ "nat" → []byte → 排序 → "ant" │ │ mp["ant"] = ["tan", "nat"] │ ├─────────────────────────────────────────┤ │ "bat" → []byte → 排序 → "abt" │ │ mp["abt"] = ["bat"] │ └─────────────────────────────────────────┘
收集 map 的所有 value: 输出: [["eat","tea","ate"], ["tan","nat"], ["bat"]]
|
🔹 完整代码示例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| func groupAnagrams(strs []string) [][]string { mp := make(map[string][]string) for _, str := range strs { s := []byte(str) sort.Slice(s, func(i, j int) bool { return s[i] < s[j] }) key := string(s) mp[key] = append(mp[key], str) } result := make([][]string, 0, len(mp)) for _, group := range mp { result = append(result, group) } return result }
|
🔹 小结:关键步骤与原因
| 步骤 |
代码 |
原因 |
| 转字节切片 |
[]byte(str) |
string 不可变,转成可修改的 []byte |
| 排序 |
sort.Slice(s, ...) |
按 ASCII 码升序排列字节,异位词排序后相同 |
| 转回字符串 |
string(s) |
排完序转回 string,作为 map 的 key |
| 分组 |
mp[key] = append(...) |
相同 key 的字符串归为一组 |
整体思路:异位词排序后相同 → 用排序结果做 key 分组 → 一次遍历完成分类
🔹 进阶技巧
✅ 预分配容量(提升性能)
1 2 3 4 5
| mp := make(map[string][]string, len(strs)/2)
mp[key] = append(mp[key], str)
|
✅ 使用 sort.Strings 排序字符串切片
如果需要对多个字符串整体排序(而非单个字符串内部):
1 2
| words := []string{"banana", "apple", "cherry"} sort.Strings(words)
|
✅ 处理 Unicode 字符
[]byte 只适合 ASCII 字符,处理中文等多字节字符需用 []rune:
1 2 3 4 5 6
| str := "你好世界"
runes := []rune(str) sort.Slice(runes, func(i, j int) bool { return runes[i] < runes[j] }) sorted := string(runes)
|
| 类型 |
适用场景 |
注意事项 |
[]byte |
ASCII / 英文 |
每个元素 1 字节,效率高 |
[]rune |
Unicode / 中文 |
每个元素 4 字节,支持多语言 |
🔑 关键要点速记
| 要点 |
说明 |
| string 不可变 |
必须先转 []byte 或 []rune 才能排序 |
sort.Slice |
通用排序函数,需传入比较函数 |
| 比较的是码值 |
byte 比较 ASCII,rune 比较 Unicode 码点 |
| 异位词分组 |
排序后相同 → 可作为 map key 聚合 |
| 转回 string |
string(s) 会拷贝底层数据,生成新字符串 |
| Unicode 支持 |
中文等多字节字符需用 []rune |
📌 一句话总结:字符串排序的本质是”转切片→排序→转回”,理解 string 的不可变性和 sort.Slice 的比较机制,是处理字母异位词等字符串算法题的关键。