49. Group Anagrams(字母异位词分组)
1. 题目描述
给定一个字符串数组,将字母异位词组合在一起。字母异位词指字母相同,但排列不同的字符串。
示例:
输入: [“eat”, “tea”, “tan”, “ate”, “nat”, “bat”]
输出:
[
[“ate”,“eat”,“tea”],
[“nat”,“tan”],
[“bat”]
]
说明:
- 所有输入均为小写字母。
- 不考虑答案输出的顺序。
2. 哈希表(Hash Table)
这题主要的思路是使用哈希表来映射每个字符串,把字母异位词映射到一起,所以一共有三种思路:1)使用sort把所有字符串按照字母表的顺序进行排序;2)把每个字符映射到数组中,以这个数组来进行映射;3)使用质数相乘唯一性来映射字符串。第三种方法我们单独提出来讲解,这里先讲讲第一种和第二种方法。另外,三种方法的代码都合并到“4. 实例代码”里面了。
2.1 使用Sort的哈希表
使用sort直接按字母表顺序排序(即字母从小到大进行排序)是最直接的方法了,比如下面这个例子:
abc -> abc
acb -> abc
abc -> abc
bac -> abc
上面几个字符串排序以后都可以映射到同一个字符串"abc":,我们用排序后的字符串作为key,string数组作为value即可解决问题啦,so easy~
2.2 使用count数组的哈希表
除了使用排序进行映射,我们还可以使用数组记录字符串中每个字符的数目来进行映射,举个例子哒:
abc -> [1,1,1,后面的0省略]
acb -> [1,1,1,后面的0省略]
abc -> [1,1,1,后面的0省略]
bac -> [1,1,1,后面的0省略]
我们知道字母在计算机也是用数字来表示的,比如’a’用数字97表示,所以我们用小写字母减去’a’,就能把字母映射到一个长度为26的数组之中(这个方法在字符串题目中非常常用,大家可以用小本本记下啦),然后我们计数每个字母出现的次数,然后把26个位置的计数都转化成一个字符串作为key即可。比如上面的例子,只有0,1,2的位置为1,其他都为0,所以这些字符串可以映射成:
[1,1,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0] -> “11100000000000000000000000”
我看到其他同样的解法里面,会在数字前面加’#’,写成“#1#1#1…”,不是很懂这样做的理由,我这里实际提交的代码没有加其他任何字符,也是可以通过测试的,可能是其他语言有什么问题需要添加其他字符用于区别。
3. 质数相乘(Prime Multiplication)
这里需要用到一个数学知识:
每个自然数,除了质数本身,都可以分解成质数的乘积。
我们可以反过来利用质数不可分解性,所以不同的质数的乘积是唯一的,因而和上面把字母映射到连续数组进行计数不同,这次我们把字母映射到26个不同的质数:
[ 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103 ]
然后用它们的乘积表示key,比如:
abc -> 2 * 3 * 5 = 30
acb -> 30
bac -> 30
———————————
bcd -> 3 * 5 * 7 = 105
bdc -> 105
dcb -> 105
最后我们需要注意,这样用乘积表示key,如果用带符号的long和int会溢出,所以这里我们用无符号的unisgned long来表示乘积,避免结果溢出。
4. 实例代码
class Solution {
unordered_map<string, vector<string>> m;
vector<vector<string>> ans;
vector<int> primes = { 2, 3, 5, 7, 11, 13, 17, 19, 23, 29, 31, 41, 43, 47, 53, 59, 61, 67, 71, 73, 79, 83, 89, 97, 101, 103 };
unordered_map<unsigned long, vector<string>> m1;
// 下面三个方法选一个即可
// (1)使用sort的哈希表
void usingSort(string& str) {
string temp = str;
sort(temp.begin(), temp.end());
if (m.count(temp)) m[temp].push_back(str);
else m.insert(pair<string, vector<string>>(temp, { str }));
}
// (2)使用计数的哈希表
void usingCounting(string& str) {
vector<int> arr(26, 0);
for (char c : str) arr[c - 'a']++;
string temp;
for (int num : arr) temp.push_back(num + '0');
if (m.count(temp)) m[temp].push_back(str);
else m.insert(pair<string, vector<string>>(temp, { str }));
}
// (3)使用质数相乘的哈希表
void usingPrimes(string& str) {
unsigned long sum = 1; // long 和 int 类型会overflow
for (char c : str) sum *= primes[c - 'a'];
if (m1.count(sum)) m1[sum].push_back(str);
else m1.insert(pair<unsigned long, vector<string>>(sum, { str }));
}
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
for (string& str : strs) {
usingSort(str);
//usingCounting(str);
//usingPrimes(str);
}
for (auto str : m) ans.push_back(str.second);
//for (auto str : m1) ans.push_back(str.second); // 仅(3)使用
return ans;
}
};