【LeetCode】1032. 字符流(hard)——字典树、自动机、前缀树、Trie树

【题目链接】

https://leetcode.cn/problems/stream-of-characters/

【题目大意】

给定一个单词数组和一个字符流,根据实时字符流的后缀查找单词数组中是否有匹配的单词,若有返回 t r u e true true ,否则返回 f a l s e false false

【输入示例】

输入:
[“StreamChecker”, “query”, “query”, “query”, “query”, “query”, “query”, “query”, “query”, “query”, “query”, “query”, “query”]
[[[“cd”, “f”, “kl”]], [“a”], [“b”], [“c”], [“d”], [“e”], [“f”], [“g”], [“h”], [“i”], [“j”], [“k”], [“l”]]
输出:
[null, false, false, false, true, false, true, false, false, false, false, false, true]
解释:
StreamChecker streamChecker = new StreamChecker([“cd”, “f”, “kl”]);
streamChecker.query(“a”); // 返回 False
streamChecker.query(“b”); // 返回 False
streamChecker.query(“c”); // 返回n False
streamChecker.query(“d”); // 返回 True ,因为 ‘cd’ 在 words 中
streamChecker.query(“e”); // 返回 False
streamChecker.query(“f”); // 返回 True ,因为 ‘f’ 在 words 中
streamChecker.query(“g”); // 返回 False
streamChecker.query(“h”); // 返回 False
streamChecker.query(“i”); // 返回 False
streamChecker.query(“j”); // 返回 False
streamChecker.query(“k”); // 返回 False
streamChecker.query(“l”); // 返回 True ,因为 ‘kl’ 在 words 中

【数据范围】

  • 1 <= words.length <= 2000
  • 1 <= words[i].length <= 200
  • words[i] 由小写英文字母组成
  • letter 是一个小写英文字母
  • 最多调用查询 4 ∗ 1 0 4 4 * 10^4 4104

模拟TLE

  既然是后缀匹配,那存在正确匹配的条件一定是字符流中的当前字符是某一单词的结束字母。因此,我们先按结束字母将单词分类存储,每次用字符流的当前字母向前构建单词,并查找以当前字母为结尾的单词列表。我们可以记录每个单词列表的最大单词长度,限制每次前向构建单词的最大长度。忽略初始化和哈希表的查找时间,理论上只需要 200 ∗ 4 ∗ 1 0 4 200*4 * 10^4 2004104,但是依然不在时间限制范围内,不出所料TLE了。

class StreamChecker {
public:
    map<char,set<string>> st;//根据尾字符将单词分组
    map<char,int> mp;//记录每个组里面单词的最大长度,也可以不要这个,默认单词最大长度200
    string ss;
    StreamChecker(vector<string>& words) {
        for(auto v:words){
            char c=v[v.length()-1];
            st[c].insert(v);
            if(mp[c]<=v.length()) mp[c]=v.length();
        }
    }
    
    bool query(char letter) {
        ss+=letter;
        auto s=st[letter];
        if(!s.empty()){//有以这个字符结尾的单词
        string w="";
            for(int i=0;i<mp[letter]&&i<ss.length();i++){//向前拼接新单词并搜索
                w=ss[ss.length()-1-i]+w;
                if(s.count(w)) return true;
            }
        }
        return false;
    }
};

/**
 * Your StreamChecker object will be instantiated and called as such:
 * StreamChecker* obj = new StreamChecker(words);
 * bool param_1 = obj->query(letter);
 */

优化

  解题思路保持不变,但是优化查找过程。我们创建一个自动机,或者说前缀树。通过树的可达性来判断单词的存在与否。这棵前缀树包含两个属性,一个是子节点 c h i l d r e n [ 26 ] children[26] children[26] 和结束标志 i s E n d isEnd isEnd 。首先遍历每一个单词,每个单词从根节点向下延伸,在字典树中创建一条自己的路径并将结尾标志置为 t r u e true true 。每次查找时,也从这个树的根节点根据当前字符往下走,如果去到了不可达的地方,则不匹配,如果遇到上了 i s E n d = t r u e isEnd=true isEnd=true ,则匹配成功。这和有限自动状态机很相似,所以也叫AC自动机。具体细节看代码注释。

class StreamChecker {
public:
    struct Node{
        vector<Node*> children;
        bool isEnd;//重点标识
        Node():children(26),isEnd(false){}//初始化属性值,LeetCode不做这个会报错
    }; 
    string s;//字符流
    Node* trie=new Node();//树的根节点
    StreamChecker(vector<string>& words) {
        for(auto w:words){
            Node* node=trie;
            for(int i=w.size()-1;i>=0;i--){
                int idx=w[i]-'a';
                if(node->children[idx]==NULL) node->children[idx]=new Node();//如果不存在就创建一个节点
                node=node->children[idx];//存在则往下走
            }
            node->isEnd=true;//单词结束,设置终点
        }
    }
    
    bool query(char letter) {
        s+=letter;
        Node* node=trie;
        for(int i=0,j=s.size()-1;i<200&&j>=0;i++,j--){//结束条件为达到单词上限200或字符流遍历结束
            int idx=s[j]-'a';
            if(node->children[idx]==NULL) return false;//进入了不可达状态
            node=node->children[idx];
            if(node->isEnd) return true;//匹配成功
        }
        return false;
    }
};

/**
 * Your StreamChecker object will be instantiated and called as such:
 * StreamChecker* obj = new StreamChecker(words);
 * bool param_1 = obj->query(letter);
 */
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
KMP算法是一种字符串匹配算法,用于在一个文本串S内查找一个模式串P的出现位置。它的时间复杂度为O(n+m),其中n为文本串的长度,m为模式串的长度。 KMP算法的核心思想是利用已知信息来避免不必要的字符比较。具体来说,它维护一个next数组,其中next[i]表示当第i个字符匹配失败时,下一次匹配应该从模式串的第next[i]个字符开始。 我们可以通过一个简单的例子来理解KMP算法的思想。假设文本串为S="ababababca",模式串为P="abababca",我们想要在S中查找P的出现位置。 首先,我们可以将P的每个前缀和后缀进行比较,得到next数组: | i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | | --- | - | - | - | - | - | - | - | - | | P | a | b | a | b | a | b | c | a | | next| 0 | 0 | 1 | 2 | 3 | 4 | 0 | 1 | 接下来,我们从S的第一个字符开始匹配P。当S的第七个字符和P的第七个字符匹配失败时,我们可以利用next[6]=4,将P向右移动4个字符,使得P的第五个字符与S的第七个字符对齐。此时,我们可以发现P的前五个字符和S的前五个字符已经匹配成功了。因此,我们可以继续从S的第六个字符开始匹配P。 当S的第十个字符和P的第八个字符匹配失败时,我们可以利用next[7]=1,将P向右移动一个字符,使得P的第一个字符和S的第十个字符对齐。此时,我们可以发现P的前一个字符和S的第十个字符已经匹配成功了。因此,我们可以继续从S的第十一个字符开始匹配P。 最终,我们可以发现P出现在S的第二个位置。 下面是KMP算法的C++代码实现:
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值