题目:
Given an array of strings, return all groups of strings that are
anagrams.
Note: All inputs will be in lower-case.
class Solution {
public:
vector<string> anagrams(vector<string> &strs) {
}
};
题意本身并不是很清晰,开始我的代码总是报Output Limit Exceeded,后来搜了相关文章,明白了题目真正要求的输出格式。
For example:
Input: ["tea","and","ate","eat","den"]
Output: ["tea","ate","eat"]
开始,我的思路是,将每一个string 都和其他比较,互为anagram的就记录到vector<string>
res中。最后返回res。
这样宏观上来需要O(n2)次,n是输入vector的元素个数;对于内部判断anagram,我自己用数组实现dictionary[26],记录每一个character出现的次数,两个string如果正好可以让dictionary的全部元素回归0,则互为anagram,这样内部判断的时间是O(m),m是string的长度。
写这段代码时,我对输出的理解还存在错误,以为对于所有anagram
group,只要将这个group中的第一个放入返回的vector<string>中即可。所以下面代码中,如果res中后面的元素已经判定和res中靠前的string互为anagram,后面的元素会被从res中移除。
初次实现的代码如下:
class Solution {
public:
vector<string> anagrams(vector<string> &strs) {
vector<string> res;
if(strs.size() == 0) return res;
dic = new int[26];
for(vector<string>::iterator it = strs.begin(); it < strs.end(); ++it){
res.push_back(*it);
}
for(int i = 0; i < res.size(); ++i){
for(int j = i+1; j < res.size(); ++j){
initDic(dic, 26, res[i]);
int k = 0;
for(; k < res[j].length(); ++k){ //判断 res[i] 和res[j] 是否为anagrams
dic[res[j][k] - ‘a‘]--;
if(dic[res[j][k] - ‘a‘] < 0) break;
}
if(k == res[j].length() && judgeDic(dic, 26)){
res.erase(res.begin() + j); //移除和res中的元素互为anagram的
--j;
}
}
}
return res;
}
private:
int* dic;
void initDic(int* dic, int n, string str){
for(int i = 0; i < n; ++i){
dic[i] = 0;
}
for(int j = 0; j < str.length(); ++j){
dic[str[j] - ‘a‘]++;
}
}
bool judgeDic(int* dic, int n){
int i = 0;
for(; i < n; ++i){
if(dic[i] != 0) break;
}
return (i == n);
}
};
这样做,超时。
原因就在于宏观上的O(n2),应该有优化的余地。Annie
Kim‘s Blog中介绍了空间换时间的做法,即定义一个map<string,
int>,然后遍历strs的元素,对于strs中的每一个string s,先将s的内容排序,再将排好序的s当作key。
这样虽然排序本身需要O(mlogm)的时间(m是string的长度),但是宏观上,只需要O(n)的时间(n是输入vector的元素个数),因为map的访问是O(1)。
因此整体上时间复杂度可能会下降(测试用例的n较大时)。
但是这个思路的缺点在于:因为是将string
排序后本身作为key,因此如果题目增加难度,比如string中包含标点和空格,那么这种方法就不能准确判断两个string是否anagram了。另外,如果string非常长,用来做key也不是很方便。
我结合我自己的思路做了一些修改,修改后的思路中,key不是排完序的string,而是依然利用我开始代码里面的dic[26]:先从头到尾扫一遍string,然后给dic对应位置+1,然后将dic元素本身的排列作为key。这样,(1)
在有空格和标点的情况下,依然可以判断两个string是否是anagram,如果有大写字母或者数字,只需要扩张dic的大小即可;而且Key的长度为定值,这里总是26。(2)
不再需要O(mlogm)的时间复杂度,需要O(m+26) = O(m)的复杂度。
实现代码如下:
class Solution {
public:
vector<string> anagrams(vector<string> &strs) {
vector<string> res;
if(strs.size() == 0) return res;
map<string, int> rec;
dic = new int[26];
for(int i = 0; i < strs.size(); ++i){
string key = generateKeyByDic(dic, 26, strs[i]);
if(rec.find(key) == rec.end()){
rec.insert(make_pair(key, i));
}else{
if(rec[key] >= 0){
res.push_back(strs[rec[key]]);
rec[key] = -1;
}
res.push_back(strs[i]);
}
}
return res;
}
private:
int* dic;
string generateKeyByDic(int* dic, int n, string str){
for(int i = 0; i < n; ++i){
dic[i] = 0;
}
for(int j = 0; j < str.length(); ++j){
if(str[j] <= ‘z‘ && str[j] >= ‘a‘)
dic[str[j] - ‘a‘]++;
}
string key(26, ‘0‘);
for(int k = 0; k < 26; ++k){
key[k] = dic[k] + ‘0‘;
}
return key;
}
};
100 / 100 test
cases passed. Runtime: 224
ms
而是用sorted string做key的方法,数据是 100 /
100 test cases passed. Runtime: 228
ms
时间上并没有提高多少,原因应该是test case的string长度都不算大,故O(mlogm)和O(m+26) 差别不大。
总结:
不论是引用的思路,还是我的思路,核心都是使用了map<string, int>,当需要在一堆字符串中找出包含相同字符的
group,这种空间换时间的方法可以考虑。