前言:对于两个字符串 s s s 和 t t t,若要判断 t t t 是否是 s s s 的子序列,相信大家都不会陌生,一个 O ( n ) O(n) O(n) 的遍历或加上双指针的优化,都能很快地结束。但是,若要判断一系列字符串 w o r d s words words 中有几个是 s s s 的子序列呢?这时候就需要引入一个新的算法:滚动字典桶
-
先来说一说 滚动字典桶 这个新名词:其实这个词是我自己发明的,主要有三个关键词:滚动 + 字典 + 桶。滚动是因为其不像字典树那样需要根据字符串的长度不断创建新节点,而是可以不断复用数组。字典是因为其构成与 字典树 有相似之处。桶是因为这种数据结构的本质是桶存储。
-
可能上面的描述会让人一头雾水,那话不多说,先看题目和代码
class Solution {
public:
//PII 代表 {在 words 中的序号, 下一个待匹配字符}
using PII = pair<int, int>;
int numMatchingSubseq(string s, vector<string>& words) {
vector<vector<PII>> bucket(26);
int n = words.size();
for(int i = 0; i < n; ++i){
char c = words[i][0];
bucket[c - 'a'].push_back({i, 0});
}
int ans = 0;
for(char c : s){
vector<PII> old = bucket[c - 'a']; // 倒出单词
bucket[c - 'a'].clear();
for(auto& [index, next] : old){
string& t = words[index];
next ++;
if(next == t.size()){
ans ++;
}else{
bucket[t[next] - 'a'].push_back({index, next});
}
}
}
return ans;
}
};
- 现在来解释一下整个序列匹配的过程,首先我们需要创建一个容量为 26 26 26 的桶, 0 0 0 至 25 25 25 分别对应字母 a − z a-z a−z,而第 i i i 个桶中存放的单词 正在等待匹配的下一个字母均为 c h a r ( ′ a ′ + i ) char('a'+i) char(′a′+i)
- 在进行初始化后,开始对字符串 s s s 开始遍历,对于 s s s 中的每一个单词 c c c,便把 c c c 对应的桶中的所有单词 倒出,然后根据下一个待匹配的字符放进相应的桶。如果已经匹配到单词的最后一个字母,那么把答案加 1。
- 时间复杂度: O ( s . l e n g t h + ∑ i = 1 n w o r d s [ i ] . l e n g t h ) O(s.length + \sum_{i = 1}^n words[i].length) O(s.length+∑i=1nwords[i].length)
以上就是 滚动字符桶 的全部内容了,大家可以类比字典树 (前缀树)来进行比较学习,这两个算法分别针对子序列和前缀有着非常优秀的时间复杂度!