字典树基础与应用

news2024/11/25 2:47:30

字典树(Trie)

字典树(Trie)也叫前缀树,是一种针对字符串进行维护的树。

  • 其中的键通常是字符串,由节点在树中的位置决定,键保存在而不是在节点

  • 一个节点的所有子孙具有相同的前缀,也就是这个节点代表的字符串,根节点代表空字符串

下图中,1 - 4 - 8 - 13有3条边,表示字符串cab

在这里插入图片描述

初始化根节点

  • 假设字典中只有26个小写字母,则每个节点至多有26个子节点
  • is_end表示当前字符串是否在这里截止,False代表前缀,True代表末尾
class Trie:

    def __init__(self):
        self.children = [None] * 26
        self.is_end = False

插入字符串

从字典树的根开始,向下查找字符串的插入位置,对于当前字符对应的子节点,有两种情况:

  • 子节点存在,node = node.children[ch],向下查找子节点
  • 子节点不存在,创建一个新的节点,放在当前字符对应的位置上,再向下查找子节点
  • 遍历完字符串word,也就是到了word对应的最后一个节点,打上标记node.is_end = True

比如说在下面的字典树中插入字符串cat

在这里插入图片描述

查找第一个字符c存在,继续向下,a也存在,继续向下,t不存在

在这里插入图片描述

于是在a的子节点下面,创建一个新节点t,至此,cat字符串就被插入到了字典树中

    def insert(self, word: str) -> None:
        node = self
        for ch in word:
            ch = ord(ch) - ord('a')
            if not node.children[ch]:
                node.children[ch] = Trie()
            node = node.children[ch]
        node.is_end = True

查询字符串

从字典树的根开始,向下查找字符串,对于当前字符对应的子节点,有两种情况:

  • 子节点存在,node = node.children[ch],向下查找子节点
  • 子节点不存在,说明字典树中没有该前缀,返回None
  • 根据前缀查找结果,判断最后节点是否是末尾节点,如果是,说明找到了该字符串;如果不是末尾节点,说明只找到了该字符串的前缀
    def searchPrefix(self, prefix: str) -> "Trie":
        node = self
        for ch in prefix:
            ch = ord(ch) - ord('a')
            if not node.children[ch]:
                return None
            node = node.children[ch]
        return node


    def search(self, word: str) -> bool:
        node = self.searchPrefix(word)
        return node is not None and node.is_end


    def startsWith(self, prefix: str) -> bool:
        node = self.searchPrefix(prefix)
        return node is not None

完整代码

对应Leetcode上的题目:208. 实现 Trie (前缀树) - 力扣(Leetcode)

class Trie:

    def __init__(self):
        self.children = [None] * 26
        self.is_end = False


    def insert(self, word: str) -> None:
        node = self
        for ch in word:
            ch = ord(ch) - ord('a')
            if not node.children[ch]:
                node.children[ch] = Trie()
            node = node.children[ch]
        node.is_end = True
    
    
    def searchPrefix(self, prefix: str) -> "Trie":
        node = self
        for ch in prefix:
            ch = ord(ch) - ord('a')
            if not node.children[ch]:
                return None
            node = node.children[ch]
        return node


    def search(self, word: str) -> bool:
        node = self.searchPrefix(word)
        return node is not None and node.is_end


    def startsWith(self, prefix: str) -> bool:
        node = self.searchPrefix(prefix)
        return node is not None

字典树的应用

1803. 统计异或值在范围内的数对有多少 - 力扣(Leetcode)

给你一个整数数组 nums (下标 从 0 开始 计数)以及两个整数:lowhigh ,请返回 漂亮数对 的数目。

漂亮数对 是一个形如 (i, j) 的数对,其中 0 <= i < j < nums.lengthlow <= (nums[i] XOR nums[j]) <= high

  • 1 < = n u m s . l e n g t h < = 2 ∗ 1 0 4 1 <= nums.length <= 2 * 10^4 1<=nums.length<=2104
  • 1 < = n u m s [ i ] < = 2 ∗ 1 0 4 1 <= nums[i] <= 2 * 10^4 1<=nums[i]<=2104
  • 1 < = l o w < = h i g h < = 2 ∗ 1 0 4 1 <= low <= high <= 2 * 10^4 1<=low<=high<=2104

题目求解异或结果在 [low, high]之间的数对个数,可以转换为求解异或结果在(0, high](0, low)的个数之差

f ( x ) f(x) f(x)表示数组中异或结果小于x的数对个数,问题转换为求解 f ( h i g h + 1 ) − f ( l o w ) f(high+1)-f(low) f(high+1)f(low)

看到这题第一个想到的是暴力遍历nums,两两取异或,根据异或结果计数,这是我第一次写的代码,毫无疑问超时了

class Solution:
    def countPairs(self, nums: List[int], low: int, high: int) -> int:
        n = len(nums)
        ans = 0
        for i in range(n-1):
            for j in range(i+1, n):
                if low <= nums[i] ^ nums[j] <= high:
                    ans += 1
        return ans

怎么在这题使用字典树呢?

自己用笔写一下,我们比较nums[i]^nums[j]与x的结果时,怎么比较最快?答案是将nums[i]nums[j]和x都转换为二进制,为了表示方便,将nums[i],nums[j],x写作a,b,c,分别转为二进制数 a i a i − 1 . . . a 2 a 1 , b i b i − 1 . . . b 2 b 1 , c i c i − 1 . . . c 2 c 1 a_ia_{i-1}...a_2a_1,b_ib_{i-1}...b_2b_1,c_ic_{i-1}...c_2c_1 aiai1...a2a1bibi1...b2b1cici1...c2c1,我们从高位往低位比较,当找到一个 j ( j < = i ) j(j<=i) j(j<=i),满足 a j a_j aj^ b j b_j bj< c j c_j cj时,就不会继续往下比较了,因为不管后面是什么结果,a异或b的结果都会比c小。

上面讲的比较抽象,下面用画图举例说明,nums[i]=11,nums[j]=17,x=28

在这里插入图片描述

从左往右比较,当比较到第3位时,异或结果是比x小的,所以后面就不用比较了。

鉴于这一特性,我们可以把nums转为前缀表(字典树),将nums中的元素看作二进制表示的字符串

  • 字符串只包含0和1
  • 由于 1 < = n u m s [ i ] < = 2 ∗ 1 0 4 1 <= nums[i] <= 2 * 10^4 1<=nums[i]<=2104,而 2 ∗ 1 0 4 < 2 15 2 * 10^4 < 2^{15} 2104<215,因此字符串的长度是15(高位补零就好)

初始化

每个节点除了包含两个子节点外,还有一个cnt属性,表示根结点到该节点路径为前缀的字符串个数。

class Trie:
    def __init__(self):
        self.children = [None] * 2
        self.cnt = 0

插入字符串

从字典树的根开始,向下查找字符串的插入位置,对于当前字符对应的子节点,有两种情况:

  • 子节点存在,node = node.children[ch],向下查找子节点
  • 子节点不存在,创建一个新的节点,放在当前字符对应的位置上,再向下查找子节点

每遍历一个节点,不管节点是否存在,节点的cnt都要加1

    def insert(self, word):
        node = self
        for i in range(15, -1, -1):
            # 从高位取数字
            flag = word >> i & 1
            if not node.children[flag]:
                node.children[flag] = Trie()
            node = node.children[flag]
            node.cnt += 1

查询字符串

从字典树的根开始遍历,向下查找字符串的插入位置,并记录满足条件的前缀数量

  • 子节点不存在,说明字符串这条路径到了末尾,返回累加的前缀数量
  • x是基准值,子节点存在时有两种情况:
    • 如果x的当前位为1,就加上异或结果为0的子节点的前缀数量(小于),然后走向异或结果为1的子节点node = node.children[flag ^ 1]
    • 如果x的当前位为0,就要走向异或结果为0的子节点node = node.children[flag]
    • 注意,flag ^ 1 ^ flag = 1flag ^ flag=0
    def search(self, a, x):
        node = self
        ans = 0
        for i in range(15, -1, -1):
            if not node:
                return ans
            # 基准数x的第i位数字
            y = x >> i & 1
            # 查询数a的第i位数字
            flag = a >> i & 1
            if y == 1:
                # 只有当异或结果可能为0时,才记录cnt
                if node.children[flag]:
                    ans += node.children[flag].cnt
                node = node.children[flag ^ 1]
            else:
                node = node.children[flag]
        return ans

为防止重复比较,将nums中的元素依次放入字典树,每查询一个,放入一个。

class Solution:
    def countPairs(self, nums: List[int], low: int, high: int) -> int:
        ans = 0
        tree = Trie()
        for x in nums:
            ans += tree.search(x, high + 1) - tree.search(x, low)
            tree.insert(x)
        return ans

完整代码:

class Trie:
    def __init__(self):
        self.children = [None] * 2
        self.cnt = 0

    def insert(self, word):
        node = self
        for i in range(15, -1, -1):
            flag = word >> i & 1
            if not node.children[flag]:
                node.children[flag] = Trie()
            node = node.children[flag]
            node.cnt += 1

    def search(self, a, x):
        node = self
        ans = 0
        for i in range(15, -1, -1):
            if not node:
                return ans
            # 基准数x的第i位数字
            y = x >> i & 1
            # 查询数a的第i位数字
            flag = a >> i & 1
            if y == 1:
                # 只有当异或结果可能为0时,才记录cnt
                if node.children[flag]:
                    ans += node.children[flag].cnt
                node = node.children[flag ^ 1]
            else:
                node = node.children[flag]
        return ans


class Solution:
    def countPairs(self, nums: List[int], low: int, high: int) -> int:
        ans = 0
        tree = Trie()
        for x in nums:
            ans += tree.search(x, high + 1) - tree.search(x, low)
            tree.insert(x)
        return ans

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

PLGA-MAL/COOH/NH2基团封端聚(D,L-丙交酯-co-乙交酯)共聚物;可以用于制备胶束或者囊泡等新型靶向材料

1. HO-PLGA-Mal 马来酰亚胺封端聚&#xff08;D&#xff0c;L-丙交酯-co-乙交酯&#xff09;共聚物中文名称 马来酰亚胺封端聚&#xff08;D&#xff0c;L-丙交酯-co-乙交酯&#xff09;共聚物英文名称 HO-PLGA-Mal分子量&#xff1a;1000-100000之间选择 比例&#xff08;LA…

Golang UDP IPV6 编程

一、网络通信的步骤 服务端 1.socket() 创建通信的套接字socket:ip:addr 2.bind() 将套接字绑定监听的地址和端口号&#xff0c;作用是&#xff1a;服务器一般有很多网卡&#xff0c;多个IP地址&#xff0c;bind指定具体监听 哪一个IP地址及对应的端口号&#xff08;若服务…

消防应急物资智能仓储管理系统

一&#xff0c;项目背景消防应急物资装备管理系统(智装备 DW-S302)是一套成熟系统&#xff0c;依托互 3D 技术、云计算、大数据、RFID 技术、数据库技术、AI、视频分析技术对 RFID 智能仓库进行统管理、分析的信息化、智能化、规范化的系统。基于物联网技术&#xff0c;实现装备…

win10录屏快捷键是什么?录屏快捷键怎么设置

录屏是我们在学习和工作&#xff0c;经常会使用得到的工具。不管是录制网课视频&#xff0c;还是录制线上办公会议&#xff0c;都可以使用得上。在win10电脑系统中&#xff0c;就有录屏的功能&#xff0c;还自带了录屏快捷键。那win10录屏快捷键是什么&#xff1f;今天就给大家…

通过SwitchyOmega插件实现Chrome的PAC模式代理网络连接

通过SwitchyOmega插件实现Chrome的PAC模式代理网络连接 文章目录通过SwitchyOmega插件实现Chrome的PAC模式代理网络连接SwitchyOmega的安装PAC脚本示例示例 1&#xff1a;如果主机是本地主机&#xff0c;则直接连接示例 2&#xff1a;如果主机在防火墙内部&#xff0c;则直接连…

华为云、阿里云、腾讯云、百度云、浪潮云、金山云-云计算能力横向对比

导读 近些年&#xff0c;各大公司和码农们对云服务需求日益增长&#xff0c;大家不断在问“做云服务和物联网服务器的到底哪家强&#xff1f;”。网上也有不少人给出简单的答案&#xff0c;“明显是腾讯云&#xff0c;华为云的应用还没那么广泛&#xff0c;目前最好的有三个&am…

SpringBoot统一功能处理实现

⭐️前言⭐️ 这篇文章主要介绍Spring Boot的统一功能处理模块&#xff0c;也是AOP的实战环节。 &#x1f349;欢迎点赞 &#x1f44d; 收藏 ⭐留言评论 &#x1f4dd;私信必回哟&#x1f601; &#x1f349;博主将持续更新学习记录收获&#xff0c;友友们有任何问题可以在评论…

ORB-SLAM2 --- KeyFrameDatabase::DetectLoopCandidates函数

目录 1.函数作用 2.函数流程 3.函数解析 3.1 传入参数解析 3.2 找出和当前帧具有公共单词的所有关键帧&#xff0c;不包括与当前帧连接的关键帧 3.3 统计上述所有闭环候选帧lKFsSharingWords中与当前帧具有共同单词最多的单词数&#xff0c;用来决定相对阈值 3.4 遍…

java基于ssm的图书管理系统图书借阅管理网站图书管理网站源码

简介 本项目是图书借阅管理系统&#xff0c;主要实现了对图书的管理和借阅。 演示视频 https://www.bilibili.com/video/BV14p4y1H7GQ/?share_sourcecopy_web&vd_sourceed0f04fbb713154db5cc611225d92156 技术 ssmjqueryajaxmysql 角色 管理员学生 功能 管理员&am…

带着刚刷题的你一步步学会刷题:989. 数组形式的整数加法

这是一道很经典的题目啊&#xff0c;考的就是数字数组转换&#xff0c;思路不难&#xff0c;但是在写的时候一步一步改代码&#xff0c;去优化复杂度&#xff0c;也是有助于学习的&#xff0c;今天刷了一下&#xff0c;也分享出来&#xff0c;建议刚开始刷题的友友们可以做一做…

【 java 集合】Map 接口常用实现类对比

&#x1f4cb; 个人简介 &#x1f496; 作者简介&#xff1a;大家好&#xff0c;我是阿牛&#xff0c;全栈领域优质创作者。&#x1f61c;&#x1f4dd; 个人主页&#xff1a;馆主阿牛&#x1f525;&#x1f389; 支持我&#xff1a;点赞&#x1f44d;收藏⭐️留言&#x1f4d…

P9 PyTorch 导数,偏微分,梯度

参考&#xff1a; 多元函数的偏导数、方向导数、梯度以及微分之间的关系思考 - 知乎 关于梯度下降与Momentum通俗易懂的解释_ssswill的博客-CSDN博客_有momentum之后还要梯度剪裁吗 前言&#xff1a; 这里简单了解一下 导数 梯度 微分的概念。 在前面矩阵求导术里面介绍过 梯…

表格控件DHTMLX Spreadsheet 5.0版本重大更新,新增搜索和过滤数据、合并单元格、自动宽度功能

DHTMLX Spreadsheet是用纯JavaScript编写的开源电子表格小部件&#xff0c;可让您快速在网页上添加类似于Excel的可编辑数据表。高度可定制的JavaScript电子表格组件&#xff0c;具有优雅的Material样式&#xff0c;可安全、方便地编辑和格式化数据。本文给大家讲解DHTMLX Gant…

4种大文件传输工具和软件,用于共享大文件

无论是个人还是与团队一起工作&#xff0c;大文件传输软件和网站都能协助提高工作效率、有效地管理工作内容。疫情原因有时我们不得不居家办公&#xff0c;在这种情况下可以分享文件的工具就显得尤为重要。 每个公司都需要一个文件传输软件&#xff0c;让员工可以上传和分享他…

【华为机试真题详解】不含 101 的数(二)【2022 Q4 | 100分】

文章目录 前言题目解析参考代码前言 《华为机试真题详解 Python实现》专栏含牛客网华为专栏、华为面经试题、华为OD机试真题。 如果您在准备华为的面试,期间有想了解的可以私信我,我会尽可能帮您解答,也可以给您一些建议! 本文解法非最优解(即非性能最优),不能保证通过…

Docker 从入门到精通

目录 一、安装Docker 1. 安装必要的包 2. 设置镜像的仓库 3. 更新软件包的索引 4. 安装docker 5. 启动docker 6. 测试 hello-world 7. 查看的hello-world镜像 8. 卸载docker 9. 配置阿里云镜像加速 二、Docker 常用命令 镜像命令 &#xff08;1&#xff09;docker …

4.2 集成运放中的电流源电路

集成运放电路中的晶体管和场效应管&#xff0c;除了作为放大管外&#xff0c;还构成电流源电路&#xff0c;为各级提供合适的静态电流&#xff1b;或作为有源负载取代高阻值的电阻&#xff0c;从而提高放大电路的放大能力。 一、基本电流源电路 1、镜像电流源 图4.2.1所示为…

SAP FICO 内部订单解析

内部订单提供成本控制和短期的工作与任务的监测。内部订单通常被用于内部工作和任务的计划、信息收集和成本清算等等&#xff0c;如市场营销活动和工作修复等。成本中心用于长期成本管理&#xff0c;而内部订单则用于中短期的成本征收。整个订单生命周期过程&#xff08;从订单…

mysqldump命令备份数据库数据

#MySQLdump常用 mysqldump -u root -p --databases 数据库1 数据库2 > xxx.sql备份全部数据库的数据和结构mysqldump -u root -hlocalhost -p123456 -A > /data/mysqlDump/mydb.sql备份全部数据库的结构(加-d参数)mysqldump -u root -hlocalhost -p123456 -A -d > /da…

【测试】Bug篇

努力经营当下&#xff0c;直至未来明朗&#xff01; 文章目录一. 如何创建Bug二&#xff0e;Bug的级别三&#xff0e;Bug的生命周期四&#xff0e;跟开发起争执怎么办【高频面试题】:sparkles:小结普通小孩也要热爱生活&#xff01; 一. 如何创建Bug 创建Bug的要素&#xff1…