Trie树专题

Intro:

        为了高效的存储和查找字符串,集合的数据结构。,

 板子:

/*Trie树 —— 模板题 AcWing 835. Trie字符串统计*/
int son[N][26], cnt[N], idx;
// 0号点既是根节点,又是空节点
// son[][]存储树中每个节点的子节点
// cnt[]存储以每个节点结尾的单词数量
 
// 插入一个字符串
void insert(char *str)
{
    int p = 0;
    for (int i = 0; str[i]; i ++ ){
        int u = str[i] - 'a';
        if (!son[p][u]) son[p][u] = ++ idx;
        p = son[p][u];
    }
    cnt[p] ++ ;
}
 
// 查询字符串出现的次数
int query(char *str)
{
    int p = 0;
    for (int i = 0; str[i]; i ++ ){
        int u = str[i] - 'a';
        if (!son[p][u]) return 0;
        p = son[p][u];
    }
    return cnt[p];
}
// 正常遍历完后的p就是指向最后一个字符。

AcWing 835. Trie字符串统计 

#include<iostream>
using namespace std;
int t;
const int N = 1e5 + 10;
int son[N][27], cnt[N], idx;
string x;

void insert(string str){
    int p = 0;
    for(int i = 0; i < str.length(); i ++){
        int u =  str[i] - 'a';
        if(!son[p][u])  son[p][u] = ++ idx;
        p = son[p][u];
    }
    cnt[p] ++;
}

int query(string str){
    int p = 0;
    for(int i = 0; str[i]; i ++){
        int u = str[i] - 'a';
        if(!son[p][u])  return 0;
        p = son[p][u];
    }
    return cnt[p];
}

int main(){
    cin >> t;
    while (t --){
        string op;  cin >> op >> x;
        if(op[0] == 'I')    insert(x);
        else    cout << query(x) << endl;
    }
    return 0;
}

======异或字典树/01字典树======

AcWing 143. 最大异或对

        着重关注一下如何估计结点数目的上限M. 

#include<iostream>
using namespace std;
const int N = 1e5 + 10, M = 30 * N + 10;    
// M是估计节点数目的上限 = 单词个数(N) * 平均单词长度(30)
int a[N], son[M][2], idx;

void insert(int x){
    int p = 0;
    for(int i = 0; i < 31; i ++){
        // 错误写法:int u = x & (1 << (30 - i));   与的结果并非-或1
        int u = (x >> (30 - i)) & 1;
        if(!son[p][u])  son[p][u] = ++ idx;
        p = son[p][u];  // 移动
    }
}

// func: 能够与num异或运算得到最大数值的数(在数组中)
int query(int num){
    int p = 0;
    int res = 0;
    for(int i = 0; i < 31; i ++){
        int u = (num >> (30 - i)) & 1;
        if(!son[p][!u]) res = (res << 1) + u, p = son[p][u];        // 相反方向不存在
        else    res = (res << 1) + !u, p = son[p][!u];              // 相反方向存在
    }
    return res;
}

int main(){
    int n;  cin >> n;
    for(int i = 0; i < n; i ++) {
        cin >> a[i];
        insert(a[i]);
    }
    int res = 0;
    for(int i = 0; i < n; i ++)
        res = max(res, a[i] ^ query(a[i]));
    cout << res;
    return 0;
}

AcWing 3485. 最大异或和

#include<iostream>
using namespace std;
const int N = 1e5 + 10, M = 30 * N + 10;
int a[N];
int son[M][2], cnt[M], idx;
int n, m;

void insert(int x, int c){  // 带删除的insert
    int p = 0;
    for(int i = 30; i >= 0; i --){
        int u = (x >> i) & 1;
        if (!son[p][u]) son[p][u] = ++ idx;
        p = son[p][u];
        cnt[p] += c;
    }
}

int query(int num){
    int p = 0;
    int res = 0;
    for(int i = 30; i >= 0; i --){
        int u = (num >> i) & 1;
        if (cnt[son[p][!u]])    res = (res << 1) + !u, p = son[p][!u];
        else                    res = (res << 1) + u,p = son[p][u];
    }
    return res;
}
int main(){
    cin >> n >> m;
    for (int i = 1; i <= n; i ++){
        cin >> a[i];
        a[i] = a[i] ^ a[i - 1];         // 前缀(异或)和数组
    }
    
    int res = 0;
    insert(a[0], 1);    // 把0 插入进去
    for (int i = 1; i <= n; i ++){
        
        if (i >= m + 1)  insert(a[i - m - 1], -1);   
        // 维护长度不超过M的连续子数组(实际由于前缀和的计算原因,字典树共有M+1个元素)
        res = max(res, a[i] ^ query(a[i]));
        insert(a[i], 1);
    }
    cout << res;
    return 0;
}

①相当于一个滑动的窗口,动态维护这个窗口对应的Trie树,那么就可以用O(30 * lenwindow)的时间求出与它异或最大的那个数字(+前缀和的思想)就是正解。

②维护细节,实际上在判断a[i] ^ query(a[i])的时候,a[i]是没有插入到Trie树中的,也就是树中也只有M个数字。

Leetcode 208. 实现 Trie (前缀树)

注意下search和startswith的判别即可。 

class Trie {
public:
    const static int N = 3e4 * 20;
    int son[N][26], cnt[N], idx;
public:
    Trie() {
        idx = 0;
        memset(son, 0, sizeof(son));
        memset(cnt, 0, sizeof(cnt));
    }
    
    void insert(string word) {
        int p = 0;
        for(int i = 0; i < word.length(); i ++){
            int u = word[i] - 'a';
            if(!son[p][u])  son[p][u] = ++ idx;
            p = son[p][u];
        }
        cnt[p] ++;
    }
    
    bool search(string word) {
        int p = 0;
        for(int i = 0; i < word.length(); i ++){
            int u = word[i] - 'a';
            if(!son[p][u])  return false;
            p = son[p][u];
        }
        return cnt[p];
    }
    
    bool startsWith(string prefix) {
        int p = 0;
        for(int i = 0; i < prefix.length(); i ++){
            int u = prefix[i] - 'a';
            if(!son[p][u])  return false;
            p = son[p][u];
        }
        return true;
    }
};

Leetcode 676. 实现一个魔法字典 

方法一:(暴力)哈希字符串长度

class MagicDictionary {
public:
unordered_map<int, vector<string>> hash;
    MagicDictionary() {
        
    }
    
    void buildDict(vector<string> dictionary) {
        for(auto& v: dictionary){
            int len = v.length();
            hash[len].push_back(v);
        }
    }
    
    bool search(string searchWord) {
        int slen = searchWord.length();
        if(hash.count(slen) == 0)   return false;
        // 如果长度相等的话,才有可能替换一个字母匹配上
        int cnt = 0;        // 变成2直接false,for结束若仍然为0也是false
        bool fd = false;
        for(auto& dicstring: hash[slen]){
            cnt = 0;
            for(int i = 0; i < slen; i ++){
                if(dicstring[i] != searchWord[i]){
                    cnt ++;
                    if(cnt == 2)    break;
                }
            }   
            if(cnt == 1){
                    fd = true;  break;
            }
        }
        return fd;
    }
};

方法二:(Trie)优化枚举+dfs

class MagicDictionary {
public:
    const static int N = 1e5 + 10;
    int son[N][26], cnt[N], idx;
public:
    MagicDictionary() {
        idx ++;
        memset(son, 0, sizeof(son));
        memset(cnt, 0, sizeof(cnt));
    }
    void buildDict(vector<string> dictionary) {
        auto insert = [&](string x) -> void{
            int p = 0;
            for(int i = 0; i < x.length(); i ++){
                int u = x[i] - 'a';
                if(!son[p][u])  son[p][u] = ++ idx;
                p = son[p][u];
            }cnt[p] ++;
        };
        for(auto& v: dictionary)    insert(v);
    }
    
    bool search(string searchWord) {
        function<bool(int, int, int)> dfs=[&](int p, int idx, bool modi)->bool{
            if(idx == searchWord.size())
                return modi && cnt[p];   // 修改过一次&&终止
            int u = searchWord[idx] - 'a';
            if(son[p][u])  // 存在的话, 可以继续往下走
                if(dfs(son[p][u], idx + 1, modi))
                    return true;
            // 如果还没修改过, 可以修改为任意除了原本元素的其他字母
            if (!modi){
                for(int i = 0; i < 26; i ++){
                    if (i != u && son[p][i])
                        if(dfs(son[p][i], idx + 1, true))
                            return true;
                }
            }
            return false;
        };
        return dfs(0, 0, false);
    }
};

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_Ocean__

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值