算法------(12)Trie树(字典树)

news/发布时间2024/5/14 20:16:17

例题:(1)Acwing 835. Trie字符串统计

        Trie树是一个可以高效存储查询字符串的数据结构。将一个字符串的每一个字符作为一个根节点,从字符串头到字符串尾连接起来。因此我们可以把每一个字符串存储为一个节点,记录其子节点的位置。存储时对每一个字符串尾进行标记,查询时从根节点开始遍历。

#include <iostream>
#include <cstring>
#include <algorithm>
#include <string>
using namespace std;
const int N = 1e5+10,M = 26;
int son[N][M],idx,cnt[N];
void insert(string str){int len = str.length(),p = 0;for(int i = 0;i<len;i++){int t = str[i] - 'a';if(!son[p][t]) son[p][t] = ++idx;p = son[p][t];}cnt[p]++; 
}
int query(string str){int len = str.length(),p = 0;for(int i = 0;i<len;i++){int t = str[i] - 'a';if(!son[p][t]) return 0;p = son[p][t];}return cnt[p];
}
int main()
{int n;scanf("%d", &n);while(n--){char op[2];scanf("%s", op);string str;cin >> str;if(op[0] == 'I'){insert(str);}else{printf("%d\n",query(str));}}return 0;
}

(2) AcWing 143. 最大异或对

        暴力做法肯定会超时,因此我们对xor运算进行考虑。

        每个数可以被看做31位的二进制数,而xor运算的最大值意味着从最高位开始尽量不同。因此可以用trie树进行存储,存储每一个数并对其与前面已存在的trie树进行xor运算(查看每一位所在的节点是否有与其不同的儿子存在,如果有则*2+1,否则*2)

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 31e5+10;
int idx,son[N][2];
void insert(int n){int p = 0;for(int i = 30;i>=0;i--){int t = n >> i & 1;if(!son[p][t]) son[p][t] = ++idx;p = son[p][t];}
}
int query(int x){int res = 0,p = 0;for(int i = 30;i>=0;i--){int t = x >> i & 1;if(son[p][!t]){p = son[p][!t];res = res*2 + 1;}else{p = son[p][t];res *= 2;}}return res;
}
int main()
{int n;scanf("%d",&n);int res = 0;for(int i = 0;i<n;i++){int x;scanf("%d", &x);insert(x);res = max(res,query(x));}printf("%d",res);return 0;
}

练习:(1)Leetcode 211 添加与搜索单词

         。。生病因此状态不佳。。

        这个Trie树的查找相比一般的Trie树更为麻烦,因为他的搜索字符串中含有万能字符‘.’,所以在匹配时需要分情况讨论,一般字符正常匹配即可,而字符为‘.’时遍历判断其对应的节点是否有子节点即可。因此需要用爆搜来实现。

class WordDictionary {
public:int idx = 0,son[250010][26] = {0};bool vis[250010] = {0};WordDictionary() {}void addWord(string word) {int len = word.length(),p = 0;for(int i = 0;i<len;i++){int u = word[i] - 'a';if(!son[p][u]) son[p][u] = ++idx;p = son[p][u];}vis[p] = true;}bool search(string word) {return dfs(word,0,0);}bool dfs(string word,int index,int root){if(index == word.length()) return vis[root];bool f = false;if(word[index] == '.'){for(int i = 0;i<26;i++){if(son[root][i] == 0) continue;f = dfs(word,index+1,son[root][i]);if(f) break;}}else{if(son[root][word[index] - 'a']){f = dfs(word,index + 1,son[root][word[index]-'a']);}}return f;}
};

(2)Acwing 161.电话列表

         题目没什么难的。。贴上来是为了让大家注意坑点。。输入不能随意跳出循环否则剩下没输入的会顺延输入导致全部错乱。。。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 600010,M = 10;
int son[N][M],idx;
bool cnt[N];
bool query(string x){int len = x.length(),p = 0;for(int i = 0;i<len;i++){int u = x[i] - '0';if(!son[p][u]) return false;p = son[p][u];if(cnt[p]) return true;}return true;
}
void insert(string x){int len = x.length(),p = 0;for(int i = 0;i<len;i++){int u = x[i] - '0';if(!son[p][u]) son[p][u] = ++idx;p = son[p][u];}cnt[p] = true;
}
void init(){idx = 0;memset(son[0],0,sizeof(son));memset(cnt,0,sizeof(cnt));
}
int main()
{int t;scanf("%d", &t);while(t--){init();int n;bool flag = 0;scanf("%d", &n);for(int i = 0;i<n;i++){string str;cin >> str;if(flag) continue;if(!query(str)) insert(str);else{printf("NO\n");flag = 1;}}if(!flag) printf("YES\n");}return 0;
}

(3)Acwing 142. 前缀统计


         对父子节点的关系还是不太清楚。。这题其实没啥难度,主要是何时求取节点标记需要好好思考。一定是先更新到子节点,再对标记值累加。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1e6+10,M = 26;
int son[N][M],idx,cnt[N];
void insert(string str){int p = 0,len = str.length();for(int i = 0;i<len;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,len = str.length(),res = 0;for(int i = 0;i<len;i++){int u = str[i] - 'a';if(!son[p][u]) break;p = son[p][u];res += cnt[p];}return res;
}
int main()
{int n,m;scanf("%d%d", &n, &m);for(int i = 0;i<n;i++){string str;cin >> str;insert(str);}for(int i = 0;i<m;i++){string str;cin >> str;printf("%d\n",query(str));}return 0;
}

(4)Acwing 5304. 最高频字符串

        讲道理其实还是没啥难度的题。。还是想记录个小细节。。

        当一个函数同时存在返回值和其他附加功能的时候(比如并查集的find函数存在路径加速和返回根节点两个作用),一定要注意不要重复调用,否则可能会出现重复累加(带权并查集也是如此。。)这题的insert函数就是很好的例子。

#include <iostream>
#include <cstring>
#include <algorithm>
using namespace std;
const int N = 1010,M = 26;
int son[N][M],idx,cnt[N];
int insert(string str){int p = 0,len = str.length();for(int i = 0;i<len;i++){int u = str[i] - 'a';if(!son[p][u]) son[p][u] = ++idx;p = son[p][u];}cnt[p]++;return cnt[p];
}
int main()
{int n;scanf("%d", &n);int res = -1;string ans = "";for(int i = 0;i<n;i++){string c;cin >> c;int k = insert(c);if(k > res || (k == res && c < ans) ){ans = c;res = k;}}cout << ans;return 0;
}

(5) Leetcode 1065.字符串的索引对

         我还以为有什么好方法。。原来也是无脑暴力。。

        把word里的字符串存入Trie树,然后枚举text中的每一个子串去Trie树中查询。

class Solution {int son[1010][26],idx;bool cnt[1010] = {0};
public:void insert(string x){int p = 0,len = x.length();for(int i=0;i<len;i++){int u = x[i] - 'a';if(!son[p][u]) son[p][u] = ++idx;p = son[p][u];}cnt[p] = true;}bool query(string x){int p = 0,len = x.length();for(int i=0;i<len;i++){int u = x[i] - 'a';if(!son[p][u]) return false;p = son[p][u];}return cnt[p];}vector<vector<int>> indexPairs(string text, vector<string>& words) {vector<vector<int>> res;int sz = words.size();for(int i = 0;i<sz;i++){insert(words[i]);}int len = text.length();for(int i = 0;i<len;i++){for(int j = 1;j<=len-i;j++){string str = text.substr(i,j);cout << str << endl;if(query(str)) res.push_back({i,i+j-1});}}return res;}
};

(6) Leetcode 14.最长公共前缀

        讲道理其实没啥难度。。想了好一会来着。。用字典树去做其实复杂了。。。不过是字典树专题。。 

        对每一个字符串先查询再存储,因为我们要找的是整个数组中字符串的公共前缀,因此我们每次查询时都需要把结果更新成最短的那个公共前缀。同时要注意特殊情况(一个字符串),第一个字符串最好先插入。

class Solution {int son[40010][26],idx;
public:void insert(string x){int p = 0,len = x.length();for(int i = 0;i<len;i++){int u = x[i] - 'a';if(!son[p][u]) son[p][u] = ++idx;p = son[p][u];}}int query(string x){int p = 0,len = x.length(),res = 0;for(int i = 0;i<len;i++){int u = x[i] - 'a';if(!son[p][u]) return res;p = son[p][u];res++;}return res;}string longestCommonPrefix(vector<string>& strs) {int sz = strs.size();int res = 100000;string ans = strs[0];insert(strs[0]);for(int i = 1;i<sz;i++){if(query(strs[i]) < res){res = query(strs[i]);ans = strs[i].substr(0,res);if(ans == "") return ans;}insert(strs[i]);}return ans;}
};

(7) Leetcode 139.单词拆分

        其实跟Trie没啥关系(因为要Trie+dfs不会。。。)用dp做的,贴上来是为了羞辱一下自己的菜。

        dp[i]指前i个字母是否能被拼凑出来,枚举前i个字母的每一个插空位j,对插空位j前面的字符串,其能否被拼凑可看dp[j],对j后面的字符,利用check函数(截取字符串)判断是否能在哈希set中找到。对于初始状态,规定dp[0] = true。

class Solution {bool dp[310];unordered_set<string> res;
public:bool check(string x){if(res.find(x) != res.end()) return true;return false;}bool wordBreak(string s, vector<string>& wordDict) {int sz = wordDict.size();for(int i = 0;i<sz;i++) res.insert(wordDict[i]);int n = s.length();dp[0] = true;for(int i = 1;i<=n;i++){for(int j = 0;j<i;j++){if(dp[j] && check(s.substr(j,i-j))){dp[i] = true;break;}}}return dp[n];}
};

 (8) Leetcode 720.词典中最长的单词

        无非就是打打标记嘛。。。and注意审题。。。

        要求其他单词“逐步”添加一个字母构成的最长单词,那么对query查找进行改装即可,只遍历数组的前n-1个字母,每一个字母处如果都有标记则符合要求。

class Solution {int son[30010][26],idx;bool cnt[30010];
public:void insert(string x){int p = 0,len = x.length();for(int i = 0;i<len;i++){int u = x[i] - 'a';if(!son[p][u]) son[p][u] = ++idx;p = son[p][u];}cnt[p] = true;}bool query(string x){int p = 0,len = x.length();if(len == 1) return true;for(int i = 0;i<len-1;i++){int u = x[i] - 'a';if(!son[p][u]) return false;p = son[p][u];if(!cnt[p]) return false;}return cnt[p];}string longestWord(vector<string>& words) {int n = words.size();for(int i = 0;i<n;i++){insert(words[i]);}string ans = "";int res = 0;for(int i = 0;i<n;i++){int len = words[i].length();string str = words[i];if(query(str) && (res < len|| (res == len && str < ans))){ans = str;res = len;}}return ans;}
};

(9) Leetcode 1268.搜索推荐系统

         一开始就没想出来。。且坑点巨多。。改也改了好久。。

        这道题对字典树的“节点”这个概念有很深的强化。对每一个节点,我们对应一个字符串的大根堆,每次访问这个节点时把当前字符串加入,并且如果该大根堆中字符串数目大于3时,将堆顶的字符串(字典序最大的那个)弹出。对于目标字符串,我们一个节点一个节点进行访问,如果遇到了无相应子节点的节点,则后面全部为空集,这里我们需要一个bool变量来记录是否已经出现该情况,如果只用continue判断会出错(p=0时也意味着p回到根节点)。由于每个节点对应着大根堆,因此每个节点得到的集合还需要取反。

class Solution {int son[200010][26],idx = 0;unordered_map<int,priority_queue<string>> y;
public:void insert(string x){int p = 0,len = x.length();for(int i = 0;i<len;i++){int u = x[i] - 'a';if(!son[p][u]) son[p][u] = ++idx;p = son[p][u]; y[p].push(x);if(y[p].size() > 3) y[p].pop();}}vector<vector<string>> query(string x){vector<vector<string>> res;int p = 0,len = x.length();bool flag = false;for(int i = 0;i<len;i++){int u = x[i] - 'a';if(flag || !son[p][u]){res.push_back({});flag = true;}else{p = son[p][u]; vector<string> t;while(y[p].size()){t.push_back(y[p].top());y[p].pop();}reverse(t.begin(),t.end());res.push_back(t);}}return res;}vector<vector<string>> suggestedProducts(vector<string>& products, string searchWord) {int n = products.size();for(int i = 0;i<n;i++){insert(products[i]);}return query(searchWord);}
};

(10) Leetcode 2707.字符串中的额外字符

        。。DP做不出啊啊啊啊啊。。。

        记n为字符串长度,我们对n([0,n-1])有两种划分方式,一种是把 s[n-1]当做额外字符,因此dp[n] = dp[n-1] + 1,另一种是查看[j,n-1]是否在字典中可查找到,如果可以则dp[n]又等于dp[j](相当于[j,n-1]已经处理好,只需要再处理[0,j-1])因此对每一个i枚举对应的j即可。

class Solution {int d[60],son[2510][26],idx;bool cnt[2510];
public:void insert(string x){int p = 0,len = x.length();for(int i = 0;i<len;i++){int u = x[i] - 'a';if(!son[p][u]) son[p][u] = ++idx;p = son[p][u];}cnt[p] = true;}bool query(string x){int p = 0,len = x.length();for(int i = 0;i<len;i++){int u = x[i] - 'a';if(!son[p][u]) return false;p = son[p][u];}return cnt[p];}int minExtraChar(string s, vector<string>& dictionary) {int n = dictionary.size(),len = s.length();for(int i = 0;i<n;i++){insert(dictionary[i]);}d[0] = 0;n = s.length();for(int i = 1;i<=s.length();i++){d[i] = d[i-1] + 1;for(int j = 0;j<i;j++){if(query(s.substr(j,i-j))) d[i] = min(d[i],d[j]);}}return d[n];}
};

         

 

 

 

 

 

 

 

 

         

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.bcls.cn/NBCj/4535.shtml

如若内容造成侵权/违法违规/事实不符,请联系编程老四网进行投诉反馈email:xxxxxxxx@qq.com,一经查实,立即删除!

相关文章

【软件测试】定位前后端bug总结+Web/APP测试分析

目录&#xff1a;导读 前言一、Python编程入门到精通二、接口自动化项目实战三、Web自动化项目实战四、App自动化项目实战五、一线大厂简历六、测试开发DevOps体系七、常用自动化测试工具八、JMeter性能测试九、总结&#xff08;尾部小惊喜&#xff09; 前言 1、Web测试中简单…

2024年全国乙卷高考文科数学备考:历年选择题真题练一练(2014~2023)

今天距离2024年高考还有三个多月的时间&#xff0c;今天我们来看一下2014~2023年全国乙卷高考文科数学的选择题&#xff0c;从过去十年的真题中随机抽取5道题&#xff0c;并且提供解析。后附六分成长独家制作的在线练习集&#xff0c;科学、高效地反复刷这些真题&#xff0c;吃…

go 1.18 不同目录package引用问题

go 1.18开始使用module了 不同的package在vs code中引用的话 需要先开启 是Go1.11版本之后 推出的版本管理工具 有点类似java的 maven工具 可以引入依赖使用 go env -w GO111MODULEon 先把这个打开 然后在创建的vs code工作目录下 执行 module gomdoule module 模块名 会生…

微服务篇之负载均衡

一、Ribbon负载均衡流程 二、Ribbon负载均衡策略 1. RoundRobinRule&#xff1a;简单轮询服务列表来选择服务器。 2. WeightedResponseTimeRule&#xff1a;按照权重来选择服务器&#xff0c;响应时间越长&#xff0c;权重越小。 3. RandomRule&#xff1a;随机选择一个可用的服…

常用的消息中间件RabbitMQ

目录 一、消息中间件 1、简介 2、作用 3、两种模式 1、P2P模式 2、Pub/Sub模式 4、常用中间件介绍与对比 1、Kafka 2、RabbitMQ 3、RocketMQ RabbitMQ和Kafka的区别 二、RabbiMQ集群 RabbiMQ特点 RabbitMQ模式⼤概分为以下三种: 集群中的基本概念&#xff1a; 集…

使用 ES|QL 优化可观察性:简化 Kubernetes 和 OTel 的 SRE 操作和问题解决

作者&#xff1a;Bahubali Shetti 作为一名运营工程师&#xff08;SRE、IT 运营、DevOps&#xff09;&#xff0c;管理技术和数据蔓延是一项持续的挑战。 简单地管理大量高维和高基数数据是令人难以承受的。 作为单一平台&#xff0c;Elastic 帮助 SRE 将无限的遥测数据&#…

分类预测 | Matlab实现KPCA-ISSA-LSSVM基于核主成分分析和改进的麻雀搜索算法优化最小二乘支持向量机故障诊断分类预测

分类预测 | Matlab实现KPCA-ISSA-LSSVM基于核主成分分析和改进的麻雀搜索算法优化最小二乘支持向量机故障诊断分类预测 目录 分类预测 | Matlab实现KPCA-ISSA-LSSVM基于核主成分分析和改进的麻雀搜索算法优化最小二乘支持向量机故障诊断分类预测分类效果基本描述程序设计参考资…

JSON:简介与基本使用

目录 什么是JSON&#xff1f; JSON的基本结构 JSON的基本使用 在JavaScript中使用JSON 创建JSON对象 解析JSON字符串 生成JSON字符串 在其他编程语言中使用JSON 总结 什么是JSON&#xff1f; JSON&#xff0c;全称为JavaScript Object Notation&#xff0c;是一种轻量…

XFF伪造 [MRCTF2020]PYWebsite1

打开题目 直接查看源码 看到一个./flag.php 访问一下 购买者的ip已经被记录&#xff0c;本地可以看到flag&#xff0c;那么使用xff或者client-ip伪造一下ip试试 bp抓包 加一个X-Forwarded-For头 得到flag

docker pullpush 生成镜像文件并push 到阿里云

pull docker docker pull ultralytics/ultralytics # 拉取yolov8的镜像仓库 docker run -it ultralytics/ultralytics # 运行镜像 conda create -n gsafety python3.8 # 创建环境 source activate gsafety # 激活环境 pip install -i https://pypi.tuna.tsinghua.edu.cn/simp…

【YOLO系列算法人员摔倒检测】

YOLO系列算法人员摔倒检测 模型和数据集下载YOLO系列算法的人员摔倒检测数据集可视化数据集图像示例&#xff1a; 模型和数据集下载 yolo行人跌倒检测一&#xff1a; 1、训练好的行人跌倒检测权重以及PR曲线&#xff0c;loss曲线等等&#xff0c;map达90%多&#xff0c;在行人跌…

二.西瓜书——线性模型、决策树

第三章 线性模型 1.线性回归 “线性回归”(linear regression)试图学得一个线性模型以尽可能准确地预测实值输出标记. 2.对数几率回归 假设我们认为示例所对应的输出标记是在指数尺度上变化&#xff0c;那就可将输出标记的对数作为线性模型逼近的目标&#xff0c;即 由此&…

一、网络基础知识

1、IP地址和端口号 1.1、IP地址 定义&#xff1a;用于在网络中唯一标识设备的地址。格式&#xff1a;通常由四个数字组成&#xff0c;以点分十进制表示&#xff0c;例如&#xff1a;192.168.0.1。(IPv4)作用&#xff1a;允许网络中的设备相互通信&#xff0c;通过IP地址可以定…

Word第一课

文章目录 1. 文件格式1.1 如何显示文件扩展名1.2 Word文档格式的演变1.3 常见的Word文档格式 3. 文档属性理解文档属性查看文档属性 4. 显示比例方式一&#xff1a; 手动调整方式二&#xff1a; 自动调整 5. 视图、窗口视图 1. 文件格式 1.1 如何显示文件扩展名 文档格式指的…

ThreadLocal“你”真的了解吗?(二)

《ThreadLocal“你”真的了解吗&#xff1f;&#xff08;一&#xff09;》这篇文章梳理了ThreadLocal的基础知识&#xff0c;同时还梳理了java中线程的创建方法以及这两者之间的关系&#xff0c;本篇文章我们将继续梳理与ThreadLocal相关&#xff0c;在上一节也提过的另一组件T…

什么是负载均衡集群?

目录 1、集群是什么&#xff1f; 2、负载均衡集群技术 3、负载均衡集群技术的实现 4、实现效果如图 5、负载均衡分类 6、四层负载均衡&#xff08;基于IP端口的负载均衡&#xff09; 7、七层的负载均衡&#xff08;基于虚拟的URL或主机IP的负载均衡) 8、四层负载与七层…

板块一 Servlet编程:第八节 文件上传下载操作 来自【汤米尼克的JavaEE全套教程专栏】

板块一 Servlet编程&#xff1a;第八节 文件的上传下载操作 一、文件上传&#xff08;1&#xff09;前端内容&#xff08;2&#xff09;后端内容 二、文件下载&#xff08;1&#xff09;前端的超链接下载&#xff08;2&#xff09;后端下载 在之前的内容中我们终于结束了Servle…

C语言每日一题(61)盛最多水的容器

题目链接 力扣 11 盛最多水的容器 题目描述 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水…

【《高性能 MySQL》摘录】第 2 章 MySQL 基准测试

文章目录 2.1 为什么需要基准测试2.2 基准测试的策略2.2.1 测试何种指标 2.3 基准测试方法2.3.1 设计和规划基准测试2.3.2 基准测试应该运行多长时间2.3.3 获取系统性能和状态2.3.4 获得准确的测试结果2.3.5 运行基准测试并分析结果2.3.6 绘图的重要性 2.4 基准测试工具…

【深蓝学院】移动机器人运动规划--第6章 模型预测控制(MPC)与运动规划--笔记

0. Outline 1. Reactive Control&#xff08;反应式控制&#xff09; 控制学中的 “Reactive Control” 通常指的是一种控制策略&#xff0c;它依赖于系统对特定事件或变化的即时反应&#xff0c;而不是按照预定的计划或策略行动。这种控制往往是基于当前的传感器输入来做出决…
推荐文章