LeetCode HOT100 - 字母异位词分组
从定义出发不难想到这关键就是怎么去表示这个类,怎么设计这个哈希
比较简单的就是用排序,排完序的作为键
时间复杂度 O(nklogk),因为排序的时间是 klogk
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
unordered_map<string, vector<string>> mp;
for (auto i : strs) {
string tmp = i;
sort(tmp.begin(), tmp.end());
mp[tmp].emplace_back(i);
}
vector<vector<string>> ans;
for (auto [_, i] : mp) {
ans.emplace_back(i);
}
return ans;
}
};
还有一个方案就是记录 cnt
如果是只出现一次的话实际上就直接用 长26 的二进制来作为键就好了
但这里出现不是 01 的,所以就是要看怎么表示了,这里参考题解 https://leetcode.cn/problems/group-anagrams/solutions/520469/zi-mu-yi-wei-ci-fen-zu-by-leetcode-solut-gyoc
不过把 cnt 数组作为 key 和直接计算唯一的哈希值有所不同,也就是
- 把一个东西“唯一地编码出来”
- 给一个东西算一个“哈希值”方便查找
是不同的
代码中真正的 key 其实不是哈希值,而是:
array<int, 26> cnt;
比如:
"eat"的cnt是
[1,0,0,0,1,0,...,1,...]"tea"的cnt也是同一个数组"bat"的cnt就是另一个数组
所以真正区分“是不是同一组异位词”的依据是:
两个
array<int,26>是否完全一样
哈希值只是快速找到可能的位置
比如有 map:
unordered_map<array<int, 26>, vector<string>, ArrayHash> mp;
这里:
- key 是
array<int,26> - value 是
vector<string> ArrayHash只是告诉unordered_map这个数组该怎么算哈希值
也就是说,哈希函数不是在决定“两个 key 是否相等”,
它只是用来决定:
这个 key 大概要放到哪个桶(bucket)里
于是哈希只要求:
- 相同 key 的哈希值必须相同
- 不同 key 的哈希值尽量不同,但允许冲突
于是哈希函数里可以是
int h = 0;
for (int x : arr) {
h = h * 31 + x;
}
这样的形式,不要求每个字母的出现次数一定小于 31
题解里的
return accumulate(arr.begin(), arr.end(), 0u, [&](size_t acc, int num) {
return (acc << 1) ^ fn(num);
});
也是类似的,操作是:
- 之前的结果左移一位
- 再和当前数的哈希值异或起来
它的目标也不是“零冲突唯一编码”,而是:
尽量把数组内容打散,生成一个分布还不错的哈希值
size_t 是无符号整数类型。
无符号整数溢出时,C++ 的行为不是未定义,而是:按模 \(2^k\) 回绕
因此适合干这种“故意让数越滚越大,然后截断”的哈希混合操作。
class Solution {
public:
struct Hash {
size_t operator()(const array<int, 26>& arr) const {
size_t h = 0;
for (int x : arr) {
h = h * 31 + x;
}
return h;
}
};
vector<vector<string>> groupAnagrams(vector<string>& strs) {
unordered_map<array<int, 26>, vector<string>, Hash> mp;
for (auto s : strs) {
array<int, 26> cnt{};
for (auto c : s) {
cnt[c - 'a']++;
}
mp[cnt].push_back(s);
}
vector<vector<string>> ans;
for (auto [_, i] : mp) {
ans.push_back(i);
}
return ans;
}
};

浙公网安备 33010602011771号