[LeetCode] 在一堆字符串中找出包含相同字符的 group的较快方法,题 Anagrams

题目:

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,这种空间换时间的方法可以考虑。

时间: 2024-10-05 16:00:22

[LeetCode] 在一堆字符串中找出包含相同字符的 group的较快方法,题 Anagrams的相关文章

C语言:从p所指字符串中找出ASCII码最大的字符,将其放在第一个位置上,并将该字符前的原字符向后顺序移动。

//fun函数:从p所指字符串中找出ASCII码最大的字符,将其放在第一个位置上,并将该字符前的原字符向后顺序移动. 1 #include <stdio.h> 2 void fun( char *p ) 3 { char max,*q; int i=0; 4 max=p[i]; 5 while( p[i]!=0 ) 6 { if( max<p[i] ) 7 { max=p[i]; 8 /**********found**********/ 9 q = p + i;//先找到最大值,记录最

【c语言】第一个只出现一次的字符题目:在字符串中找出第一个只出现一次的字符

// 第一个只出现一次的字符题目:在字符串中找出第一个只出现一次的字符. // 如输入"abaccdeff",则输出'b'. #include <stdio.h> #include <string.h> char find_one(char *str) { int a[256]; int len = strlen(str); int i = 0; memset(a, 0, sizeof(a)); for (i = 0; i<len; i++) { a[st

在字符串中找出第一个只出现一次的字符

题目: 在字符串中找出第一个只出现一次的字符.如输入"abaccdeff",则输出'b'. 题目分析: 对于在字符串中找出第一个出现一次的字符,联系ASCII码表,能够得到有256中不同的字符,可以利用字符的编码,将每个字符的编码作为数组的下表,建立一个有256个元素的数组arr[256],将数组中的每一个元素对应统计字符的个数,从字符串的第一个字符进行遍历,遇到一个字符给对应的数组元素加1,当数组中的其中一个元素变到2,则输出这个元素所对应的数组下表,即就是这个字符的ASCII码.

在字符串中找出连续最长的数字串 在字符串中找出连续最长的数字串,并把这个串的长度返回

写一个函数,它的原形是int continumax(char *outputstr,char *intputstr)功能:在字符串中找出连续最长的数字串,并把这个串的长度返回,并把这个最长数字串付给其中一个函数参数outputstr所指内存.例如:"abcd12345ed125ss123456789"的首地址传给intputstr后,函数将返回9, #include<iostream> using namespace std; int ContinueMax(char *

在字符串中找出第一个只出现一次的字符。 如输入“abaccdeff”,则输出&#39;b&#39;。

在字符串中找出第一个只出现一次的字符. 如输入"abaccdeff",则输出'b'. 这个题加一些要求维度只能为n 代码如下: #include <stdio.h> #include <assert.h> char FLetter(char arry[],int len) {              int letterNumber[256] = {0};              int i = 0;              char *ch  = arr

[华为机试练习题]43.在字符串中找出连续最长的数字串

题目 描述: 请一个在字符串中找出连续最长的数字串,并把这个串的长度返回:如果存在长度相同的连续数字串,返回最后一个连续数字串: 注意:数字串只需要是数字组成的就可以,并不要求顺序,比如数字串"1234"的长度就小于数字串"1359055",如果没有数字,则返回空字符串("")而不是NULL! 样例输入 abcd12345ed125ss123058789 abcd12345ss54761 样例输出 输出123058789,函数返回值9 输出547

【C语言】在字符串中找出第一个只出现一次的字符。如输入“abaccdeff”,则输出’b’

//在字符串中找出第一个只出现一次的字符.如输入"abaccdeff",则输出'b' #include <stdio.h> #include <memory.h> char OneTime(char * str) { int data[256]; char *p = str; if (*p == '\0') return '\0'; memset(data, 0, sizeof(data)); while (*p ) { data[*p++]++; //把每种字

华为机试—字符串中找出单词排序

题目: 在给定字符串中找出单词( "单词"由大写字母和小写字母字符构成,其他非字母字符视为单词的间隔,如空格.问号.数字等等:另外单个字母不算单词):找到单词后,按照长度进行降序排序,(排序时如果长度相同,则按出现的顺序进行排列),然后输出到一个新的字符串中:如果某个单词重复出现多次,则只输出一次:如果整个输入的字符串中没有找到单词,请输出空串.输出的单词之间使用一个"空格"隔开,最后一个单词后不加空格. 要求实现函数: void my_word(charinput

华为机试—给定字符串中找出单词

题目: 在给定字符串中找出单词("单词"由大写字母和小写字母字符构成,其他非字母字符视为单词的间隔,如空格.问号.数字等等:另外单个字母不算单词):找到单词后,按照长度进行降序排序,(排序时如果长度相同,则按出现的顺序进行排列),然后输出到一个新的字符串中:如果某个单词重复出现多次,则只输出一次:如果整个输入的字符串中没有找到单词,请输出空串.输出的单词之间使用一个"空格"隔开,最后一个单词后不加空格. 要求实现函数: void my_word(charinput[