目录
一、有效的变位词
题目:
给定两个字符串 s 和 t,请判断它们是不是一组变位词。在一组变位词中,它们中的字符及每个字符出现的次数都相同,但字符的顺序不能相同。例如,"anagram" 和 "nagaram" 就是一组变位词。
分析:
第 3 章已经讨论过与变位词相关的面试题。由于变位词与字符出现的次数相关,因此可以用一个哈希表来存储每个字符出现的次数。哈希表的键是字符,而值是对应字符出现的次数。
代码实现:
class Solution {
public:
bool isAnagram(string s, string t) {
if (s.size() != t.size() || s == t)
return false;
vector<int> counts(26, 0); // 用数组模拟哈希表
for (char ch : s)
{
++counts[ch - 'a'];
}
for (char ch : t)
{
if (counts[ch - 'a'] == 0)
return false;
--counts[ch - 'a'];
}
return true;
}
};
二、变位词组
题目:
给定一组单词,请将它们按照变位词分组。例如,输入一组单词 ["eat", "tea", "tan", "ate", "nat", "bat"],这组单词可以分为 3 组,分别是 ["eat", "tea", "ate"]、["tan", "nat"] 和 ["bat"]。假设单词中只包含英文小写字母。
分析:
解决这个问题,就需要找出一组变位词共同的特性,然后依据此特性把它们分到一组。下面介绍两种方法。
2.1 - 方法一
第一种方法是把每个英文小写字母映射到一个质数,如把字母 'a' 映射到数字 2,字母 'b' 映射到数字 3,以此类推,字母 'z' 映射到第 26 个质数 101。每给出一个单词,就把单词中所有字母对应的数字相乘,于是每个单词都可以算出一个数字。例如,单词 "eat" 可以映射到数字 1562(11 * 2 * 71)。
如果两个单词互为变位词,那么它们中的字母及每个字母出现的次数都相同,由于乘法满足交换律,因此上述算法把一组变位词映射到同一个数值。例如,单词 "eat"、"tea" 和 "ate" 都会映射到数字 1562。由于每个字母都是映射到一个质数,因此不互为变位词的两个单词一定会映射到不同的数字。
因此,可以定义一个哈希表,哈希表的键是单词中字母映射的数字的乘积,而值为一组变位词。
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
int primes[26] = {
2, 3, 5, 7, 11, 13, 17, 19, 23, 29,
31, 37, 41, 43, 47, 53, 59, 61, 67, 71,
73, 79, 83, 89, 97, 101
};
unordered_map<long, vector<string>> productToAnagram;
for (const string& str : strs)
{
long product = 1;
for (char ch : str)
{
product *= primes[ch - 'a'];
}
productToAnagram[product].push_back(str);
}
vector<vector<string>> result;
for (const auto& kv : productToAnagram)
{
result.push_back(kv.second);
}
return result;
}
};
如果输入 n 个单词,平均每个单词有 m 个字母,那么该算法的时间复杂度是 O(mn)。
该算法有一个潜在的问题:由于把单词映射到数字用到了乘法,因此当该单词非常长时,乘法就有可能溢出。
2.2 - 方法二
第二种方法是把一组变位词映射到同一个单词。由于互为变位词的单词的字母及每个字母出现的次数都相同,因此如果把单词中的字母排序就会得到相同的字符串。
因此可以定义一个哈希表,哈希表的键是把单词排序得到的字符串,而值为一组变位词。
key | value |
---|---|
"aet" | ["eat", "tea", "ate"] |
"ant" | ["tan", "nat"] |
"abt" | ["bat"] |
class Solution {
public:
vector<vector<string>> groupAnagrams(vector<string>& strs) {
unordered_map<string, vector<string>> mp;
for (const string& str : strs)
{
string tmp = str;
sort(tmp.begin(), tmp.end());
mp[tmp].push_back(str);
}
vector<vector<string>> result;
for (const auto& kv : mp)
{
result.push_back(kv.second);
}
return result;
}
};
如果每个单词平均有 m 个字母,排序一个单词需要 O(mlogm) 的时间。假设总共有 n 个单词,该算法总的时间复杂度是 O(nmlogm)。
虽然该方法的时间效率不如前一种方法,但是该方法不用担心乘法可能带来的溢出问题。