给定字符串 S 和单词字典 words, 求 words[i] 中是 S 的子序列的单词个数。
示例:
输入:
S = “abcde”
words = [“a”, “bb”, “acd”, “ace”]
输出: 3
解释: 有三个是 S 的子序列的单词: “a”, “acd”, “ace”。
注意:
所有在words和 S 里的单词都只由小写字母组成。
S 的长度在 [1, 50000]。
words 的长度在 [1, 5000]。
words[i]的长度在[1, 50]。
来源:力扣(LeetCode)
链接:https://leetcode-cn.com/problems/number-of-matching-subsequences
因为 S 很长,所以寻找一种只需遍历一次 S 的方法,避免暴力解法的多次遍历。
解题思路:将所有单词根据首字母不同放入不同的桶中。例如当 words = [‘dog’, ‘cat’, ‘cop’],根据首字母不同可以分为 ‘c’ : (‘cat’, ‘cop’), ‘d’ : (‘dog’,)。换句话说,每个桶中的单词就是该单词正在等待匹配的下一个字母。在遍历 S 的同时,将匹配到单词根据下一个需要匹配的字母移动到不同的桶中。
例如,有字符串 S = ‘dcaog’:
初始化 heads = 'c' : ('cat', 'cop'), 'd' : ('dog',);
遍历 S[0] = 'd' 后,heads = 'c' : ('cat', 'cop'), 'o' : ('og',);
遍历 S[1] = 'c' 后,heads = 'a' : ('at',), 'o' : ('og', 'op');
遍历 S[2] = 'a' 后,heads = 'o' : ('og', 'op'), 't': ('t',) ;
遍历 S[3] = 'o' 后,heads = 'g' : ('g',), 'p': ('p',), 't': ('t',);
遍历 S[0] = 'g' 后,heads = 'p': ('p',), 't': ('t',)。
算法
使用长度为 26 的数组 heads 做桶,每个字母对应一个桶。访问 S 中的每个字母时,将该字母对应桶中的所有单词,根据下一个等待匹配字母放入到不同的桶中。如果已经匹配到单词的最后一个字母,那么子序列单词数加 1。
算法实现
struct Node{
//一个结构体就是一个word,这个word最多有5000个单词,每个最长50位
char word[5000][51];
int wordNum;
};
int numMatchingSubseq(char * S, char ** words, int wordsSize){
struct Node barrels[26]; //设置26个桶,极端情况是所有的单词开头字母
//都是一样的,这个桶依旧能装下
int resultNum = 0;
int strLen = strlen(S);
int index = 0;
char removeChar = 0;
char secondChar = 0;
for (int i = 0; i < 26; i++) {
//初始化26个桶
for (int j = 0; j < 5000; ++j) {
memset(barrels[i].word[j], 0, 51);
}
barrels[i].wordNum = 0; //桶内单词数用来判断该单词放在桶内位置,
//也就是word中的第几行
}
if (wordsSize <= 5000){
for (int i = 0; i < wordsSize; i++) {
//将words存入到26个桶中
int barrelIdx = words[i][0] - 'a';//判断属于哪个桶
strcpy(barrels[barrelIdx].word[barrels[barrelIdx].wordNum], words[i]);//将该单词放到第几个桶(barrels[barrelIdx),该桶内第几个位置(word[barrels[barrelIdx].wordNum)
barrels[barrelIdx].wordNum ++;
}
}
for (int j = 0; j < strLen; j++) {
//遍历S,消首字母,做桶操作
int doubleChar[26] = {
0};
char temp[51] = {
0};
removeChar = S[j];
index = (int)(removeChar - 'a');
if (index < 26){
for (int i = 0; i < barrels[index].wordNum; i++) {
if (strlen(barrels[index].word[i]) > 1){
if (barrels[index].word[i][1] == barrels[index].word[i][0]){
//首字母与第二字母相同
memset(temp, 0, 51);
strcpy(temp, barrels[index].word[i] + 1);//将首字母与第二字母相同的单词复制到temp
memset(barrels[index].word[doubleChar[index]], 0, 51);
strcpy(barrels[index].word[doubleChar[index]], temp); //消首字母后复制到原始桶
doubleChar[index] ++; //原始桶操作后单词个数记录
} else {
secondChar = barrels[index].word[i][1] - 'a'; //决定放到哪个新桶
if (secondChar < 26){
strcpy(barrels[secondChar].word[barrels[secondChar].wordNum], barrels[index].word[i] + 1); //消首字母后复制到新桶
barrels[secondChar].wordNum ++; //单词数加1
memset(barrels[index].word[i], 0, 51); //复制前word[i]所在位置清空
}
}
} else if (strlen(barrels[index].word[i]) == 1){
//最后一个字符
resultNum++; //全部字符消除,子序列个数加1
memset(barrels[index].word[i], 0, 51);
}
}
barrels[index].wordNum = doubleChar[index]; //遍历一个字符,操作一个桶,更新该桶wordNum
}
}
return resultNum;
}