1. 字母异位词分组问题解析
字母异位词(Anagram)是指由相同字母重新排列组合形成的不同单词。比如"eat"、"tea"、"ate"就是一组字母异位词。这个问题要求我们将给定字符串数组中的所有字母异位词分组归类。
1.1 问题核心难点
这个问题的关键在于如何高效判断两个字符串是否为字母异位词。最直观的方法是:
- 对每个字符串的字符进行排序,排序后相同的字符串即为字母异位词
- 但这种方法时间复杂度较高,对于n个字符串,每个字符串平均长度为k,时间复杂度为O(nklogk)
1.2 哈希表的优化思路
更高效的做法是使用哈希表:
- 设计一个合适的哈希函数,使得字母异位词具有相同的哈希值
- 可以用字符计数作为哈希键,例如"aab"可以表示为"a2b1"
- 这样时间复杂度可以降低到O(nk),因为只需要遍历每个字符串一次统计字符出现次数
2. C++实现方案详解
2.1 基本数据结构选择
在C++中,我们可以使用unordered_map作为哈希表容器:
cpp复制#include <unordered_map>
#include <vector>
#include <string>
using namespace std;
vector<vector<string>> groupAnagrams(vector<string>& strs) {
unordered_map<string, vector<string>> map;
// 实现细节将在下面展开
}
2.2 哈希键的设计
关键是如何设计哈希键来表示字符计数:
cpp复制string getKey(const string& s) {
int count[26] = {0};
for(char c : s) {
count[c - 'a']++;
}
string key;
for(int i = 0; i < 26; i++) {
if(count[i] > 0) {
key += to_string('a' + i) + to_string(count[i]);
}
}
return key;
}
2.3 完整实现代码
结合上述思路,完整解决方案如下:
cpp复制vector<vector<string>> groupAnagrams(vector<string>& strs) {
unordered_map<string, vector<string>> map;
for(const string& s : strs) {
string key = getKey(s);
map[key].push_back(s);
}
vector<vector<string>> result;
for(auto& pair : map) {
result.push_back(pair.second);
}
return result;
}
3. 性能优化与边界情况
3.1 时间复杂度分析
该算法的时间复杂度主要取决于:
- 遍历所有字符串:O(n)
- 对每个字符串计算key:O(k),k为字符串平均长度
- 总时间复杂度:O(nk)
相比排序法的O(nklogk)有明显优势,特别是当k较大时。
3.2 空间复杂度考虑
空间复杂度主要来自:
- 哈希表存储所有字符串:O(nk)
- 结果存储:O(nk)
- 总空间复杂度:O(nk)
3.3 特殊输入处理
需要考虑的边界情况包括:
- 空输入:返回空vector
- 所有字符串都相同:应归为一组
- 所有字符串都不同:每个字符串自成一组
- 包含空字符串:""应单独分组
4. 实际编码中的技巧与陷阱
4.1 哈希键生成的优化
原始getKey实现可能产生较长的字符串,可以优化为固定长度表示:
cpp复制string getKey(const string& s) {
int count[26] = {0};
for(char c : s) {
count[c - 'a']++;
}
string key;
for(int i = 0; i < 26; i++) {
key += "#" + to_string(count[i]);
}
return key;
}
4.2 避免不必要的拷贝
在C++中要注意字符串拷贝的开销:
- 使用const引用传递参数
- 使用emplace_back代替push_back
- 考虑使用string_view(C++17)减少拷贝
4.3 测试用例设计
完整的测试应包含:
cpp复制void test() {
vector<string> t1 = {"eat","tea","tan","ate","nat","bat"};
auto r1 = groupAnagrams(t1);
// 预期输出: [["bat"],["nat","tan"],["ate","eat","tea"]]
vector<string> t2 = {""};
auto r2 = groupAnagrams(t2);
// 预期输出: [[""]]
vector<string> t3 = {"a"};
auto r3 = groupAnagrams(t3);
// 预期输出: [["a"]]
}
5. 扩展思考与其他解法
5.1 排序法的实现对比
虽然时间复杂度较高,但排序法代码更简洁:
cpp复制vector<vector<string>> groupAnagrams(vector<string>& strs) {
unordered_map<string, vector<string>> map;
for(string& s : strs) {
string key = s;
sort(key.begin(), key.end());
map[key].push_back(s);
}
vector<vector<string>> result;
for(auto& pair : map) {
result.push_back(pair.second);
}
return result;
}
5.2 质数乘积法的思路
另一种巧妙解法是利用质数的唯一乘积性质:
- 为每个字母分配一个唯一的质数
- 计算字符串中所有字母对应质数的乘积
- 字母异位词的乘积结果相同
- 但需要注意大数溢出问题
5.3 多语言实现比较
同样的算法在其他语言中的实现差异:
- Java使用HashMap和Arrays.toString(count)
- Python可以使用tuple作为字典键
- Go可以使用[rune]int数组作为map键
在实际工程中,选择哪种方法取决于具体语言特性和性能需求。对于C++而言,字符计数法在大多数情况下是最优选择,特别是在处理大规模数据时。
