Given an array of strings, return all groups of strings that are anagrams.
Note: All inputs will be in lower-case.
For example:
Input: ["tea","and","ate","eat","den"]
Output: ["tea","ate","eat"]
Interface: vector<string>anagrams(vector<string>&strs);
A:
首先简单介绍一下Anagram(回文构词法)。Anagrams是指由颠倒字母顺序组成的单词,比如“dormitory”颠倒字母顺序会变成“dirty room”,“tea”会变成“eat”。
回文构词法有一个特点:单词里的字母的种类和数目没有改变,只是改变了字母的排列顺序。
由此我们可以想到,只要将几个单词按照字母顺序进行排序,就可以通过比较判断他们是否是anagrams。
思路:将字符串重新排序(sort),并把所有排序后的字符串存入multi_Map。之后利用equal_range()检索所有同key的字符串。之后存入到结果中。
背景知识:
1. equal_range():返回同key的一个迭代器的pair对象(element从first到last+1。该键关联的第一个实例,以及该键关联的最后一个实例的下一个位置)。如果找不到匹配的元素,pair对象中的两个迭代器都指向此键应该插入的位置。
equal_range()迭代: its.first 代表first元素, its.second 代表last+1元素。
auto its = map.equal_range('a');
for (auto it = its.first; it != its.second; ++it) {
cout << it->first << '\t' << it->second << endl;
}
2. map<k, v>::value_type: 返回一个pair类型,first元素对应const map<k,v>::key_type类型(键),second元素对应map<k,v>::mapped_type类型(值)(值可以修改,但是key不能修改)
map_it->first 取key; map_it->second 取value.
3. auto:
在C++11标准之前,auto关键字就被用来标识临时变量语义,在新的标准中,它的目的变成了另外两种用途。auto现在是一种类型占位符,它会告诉编译器,应该从初始化式中推断出变量的实际类型。当你想在不同的作用域中(例如,命名空间、函数内、for循环中中的初始化式)声明变量的时候,auto可以在这些场合使用。
auto i = 42; // i is an int
auto l = 42LL; // l is an long long
auto p = new foo(); // p is a foo*
使用auto经常意味着较少的代码量(除非你需要的类型是int这种只有一个单词的)。当你想要遍历STL容器中元素的时候,想一想你会怎么写迭代器代码,老式的方法是用很多typedef来做,而auto则会大大简化这个过程。
std::map<std::string, std::vector<int>> map;
for(auto it = begin(map); it != end(map); ++it)
{
}
你应该注意到,auto并不能作为函数的返回类型,但是你能用auto去代替函数的返回类型,当然,在这种情况下,函数必须有返回值才可以。auto不会告诉编译器去推断返回值的实际类型,它会通知编译器在函数的末段去寻找返回值类型。在下面的那个例子中,函数返回值的构成是由T1类型和T2类型的值,经过+操作符之后决定的。
template <typename T1, typename T2>
auto compose(T1 t1, T2 t2) -> decltype(t1 + t2)
{
return t1+t2;
}
auto v = compose(2, 3.14); // v's type is double
4.
std::unordered_multimap::count
size_type count (const key_type& k) const;
class Solution {
public:
vector<string> anagrams(vector<string> &strs) {
vector<string> res;
if(strs.size() < 2) return res;
unordered_multimap<string, int> collections;
//put everything into multimap
for(int i = 0; i < strs.size(); i++)
{
string s = strs[i];
//将字符串sort,如果是回文字符key值会一样。
sort(s.begin(), s.end());
collections.insert({s, i});
}
//loop over unique key
auto it = collections.begin();
while(it != collections.end())
{
string s = it->first;
//size_type count (const key_type& k) const;
if(collections.count(s) > 1)
{
auto range = collections.equal_range(s);
for(auto it = range.first; it != range.second; it++)
{
//按照对应的字符串索引添加到ret,it是迭代器,it->second代表map的key对应的value
res.push_back(strs[it->second]);
}
}
//让迭代器指向当前同key范围的下一个迭代器,即键大于k的第一个元素
it = collections.equal_range(s).second;
}
return res;
}
};
思路:用map<string, int>记录排序后的字符串以及首次出现的位置。
1. 从strs的第一个元素开始遍历,首先对元素进行排序得到s;
2. 在map里查找s;
3. 若不存在,将s以及该元素的下标存入map<string ,int>;
4. 若存在,首先将第一次出现s时的原始字符串存入结果res,即strs[map[s]],并将map[s]设置为-1(防止下次再存),再将该字符串本身存入结果res;
5. 重复以上1-4步,直到遍历结束。
AC Code:class Solution {
public:
vector<string> anagrams(vector<string> &strs) {
vector<string> res;
if(strs.size() < 2) return res;
unordered_map<string, int> cache;
for(int i = 0; i < strs.size(); i++)
{
string s = strs[i];
sort(s.begin(), s.end());
auto it = cache.find(s);
if(it == cache.end())
{
cache.insert({s, i});
}
else
{
//存储当前回文字符串
res.push_back(strs[i]);
if(it->second >= 0)
{
//第一次出现s时的原始字符串存入结果res,并且置为-1,防止重复写入
res.push_back(strs[it->second]);
it->second = -1;
}
}
}
return res;
}
};
本题其实降低了一点难度,就是输入的字符串全部为小写字母,这样就不需要处理大写字母,空格这些特殊情况了。