标签:style blog class code java color
题目:
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,这种空间换时间的方法可以考虑。
[LeetCode] 在一堆字符串中找出包含相同字符的 group的较快方法,题 Anagrams,布布扣,bubuko.com
[LeetCode] 在一堆字符串中找出包含相同字符的 group的较快方法,题 Anagrams
标签:style blog class code java color
原文地址:http://www.cnblogs.com/felixfang/p/3706217.html