2023.11Datewhale_Leedcode_基础数据结构篇(下)学习笔记(二)

三、字典树

1. 实现 Trie (前缀树)

要求:实现前缀树数据结构的相关类 Trie 类。

Trie 类:

  • Trie() 初始化前缀树对象。
  • void insert(String word) 向前缀树中插入字符串 word
  • boolean search(String word) 如果字符串 word 在前缀树中,返回 True(即,在检索之前已经插入);否则,返回 False
  • boolean startsWith(String prefix) 如果之前已经插入的字符串 word 的前缀之一为 prefix,返回 True;否则,返回 False

说明

  • 1 <= word.length, prefix.length <= 2000
  • word 和 prefix 仅由小写英文字母组成
  • insertsearch 和 startsWith 调用次数 总计 不超过 3 * 10e4 次
# 定义节点类
class Node:

    # 定义节点属性(初始化无参数实例对象)
    def __init__(self):
        # 节点实质上是一个哈希表
        self.ch = dict()
        # 是否为结束节点
        self.end = False


# 定义多叉树
class Trie:

    # 定义多叉树属性
    def __init__(self):
        # 定义根节点
        self.root = Node()

    # 定义插入字符串的方法
    def insert(self, word: str) -> None:
        # 当前节点指向的字典
        cur = self.root
        # 将字母作为键,建立一个空字典作为它的值,即下级节点
        for ch in word:
            # 新键就存入,旧键就跳过
            if ch not in cur.ch:
                cur.ch[ch] = Node()
            # 当前节点转向下一节点
            cur = cur.ch[ch]
        # 传入完毕,最后一个节点的属性修改为结束节点
        cur.end = True

    # 定义查找字符串的方法
    def search(self, word: str) -> bool:
        cur = self.root
        for ch in word:
            # 失配就跳出
            if ch not in cur.ch:
                return False
            cur = cur.ch[ch]
        # 顺利结束遍历,则判断下一节点是否 存在且为结束节点
        return cur is not None and cur.end       

    # 定义查找前缀的方法
    def startsWith(self, prefix: str) -> bool:
        cur = self.root
        for ch in prefix:
            if ch not in cur.ch:
                return False
            cur = cur.ch[ch]
        # 下一节点是否 存在
        return cur is not None 

         为 object 类的 dict 对象赋予更多的属性,并构建多级哈希表存储数据,合并相同数据单元,用尽量少的空间换取更多时间。

class Trie:

    def __init__(self):
        self.node = dict()


    def insert(self, word: str) -> None:
        now = self.node
        for i in word:
            if i not in now:
                now[i] = dict()
            now = now[i]
        now['end'] = 1


    def search(self, word: str) -> bool:
        now = self.node
        for i in word:
            if i not in now:
                return False
            now = now[i]

        return True if 'end' in now else False       


    def startsWith(self, prefix: str) -> bool:
        now = self.node
        for i in prefix:
            if i not in now:
                return False
            now = now[i]

        return True

         也可以选择不用 Node 类给 dict 附加属性,在字典中添加特征键 'end' 实现。

2. 键值映射

要求:实现一个 MapSum 类,支持两个方法,insert 和 sum

  • MapSum() 初始化 MapSum 对象。
  • void insert(String key, int val) 插入 key-val 键值对,字符串表示键 key,整数表示值 val。如果键 key 已经存在,那么原来的键值对将被替代成新的键值对。
  • int sum(string prefix) 返回所有以该前缀 prefix 开头的键 key 的值的总和。

说明

  • 1 <= key.length, prefix.length <= 50
  • key 和 prefix 仅由小写英文字母组成
  • 1 <= val <= 1000
  • 最多调用 50 次 insert 和 sum
class MapSum:

    def __init__(self):
        self.node = dict()

    def insert(self, key: str, val: int) -> None:
        # 生成字典树:将单词的val存放于每个字母下的子节点
        now = self.node
        for i in key:
            if i not in now:
                now[i] = dict()
                now = now[i]
                now['val'] = val
            # 若节点已开辟,则将val加入节点
            else:
                now = now[i]
                now['val'] += val

        '''检查树中是否存在重复单词'''
        # 不存在:用'end'键标记结束节点,将对应val存入该键
        if 'end' not in now:
            now['end'] = val
        # 存在
        else:
            # 交换val
            val, now['end'] = now['end'], val
            # 减去路径节点的原val,实现替换
            now = self.node
            for i in key:
                now = now[i]
                now['val'] -= val
        

    def sum(self, prefix: str) -> int:
        now = self.node 
        # 失配则非前缀
        for i in prefix:
            if i not in now:
                return 0
            now = now[i]
        # 匹配成功则返回val的和 
        return now['val']

        解法关键在于将结束节点作为替换键值对的标志和实现方式。

3. 驼峰式匹配

描述:给定待查询列表 queries,和模式串 pattern。如果我们可以将小写字母(0 个或多个)插入模式串 pattern 中间(任意位置)得到待查询项 queries[i],那么待查询项与给定模式串匹配。如果匹配,则对应答案为 True,否则为 False

要求:将匹配结果存入由布尔值组成的答案列表中,并返回。

说明

  • 1 <= pattern.length, queries.length <= 100
  • 1 <= queries[i].length <= 100
  • queries[i] 和 pattern 由英文字母组成
class Solution:
    def camelMatch(self, queries: List[str], pattern: str) -> List[bool]:

        # 正则化:取模式串大写字母
        s = re.sub('[^A-Z]','',pattern)
        for i in range(len(queries)):
            # 取列表单词
            ss = queries[i]
            # 先决条件:两串大写字母排列相同
            if s == re.sub('[^A-Z]','',ss):
                # 初始化:单词指针,过界跳出标志
                j, f = 0, 1
                # 遍历模式串字母
                for k in pattern:
                    # 失配
                    while k != ss[j]:
                        # 切换到单词的下一个字母
                        j+=1
                        # 单词索引过界
                        if j == len(ss):
                            # 原地修改为判定结果
                            queries[i] = False
                            # 过界跳出标志
                            f=0
                            break

                    j+=1    # leedcode的bug判定机制:即使还未运行某行代码,也不能索引过界
                    if not(f):break

                if f:queries[i] = True

            else:
                queries[i] = False

        return queries

        核心规则是:两串大写字母排列相同,patter 的字母依次在出现在单词中出现。

class Solution:
    def camelMatch(self, queries: List[str], pattern: str) -> List[bool]:
        
        def helper(a, b):
            # 两串指针初始化
            i, j = 0, 0
            while i < len(a) and j < len(b):
                # 匹配成功,移动模式串指针
                if a[i] == b[j]:
                    i += 1
                # 失配,确定单词中的大写字母是否先于模式串出现,或者是否是大写字母失配
                elif b[j].isupper():
                    return False
                # 单词指针移动
                j += 1
            # 当模式串匹配全部成功,单词的未配段没有大写字母,返回True,反之为False
            return i == len(a) and all(x.islower() for x in b[j:])
        
        # 利用函数和生成式实现列表类型结果的输出
        return [helper(pattern, x) for x in queries]

        利用双指针完成的匹配,其中大写字母的排列相等的判定可转化为:pattern 的首字母在单词中出现前和尾字母在单词中出现后,单词中是否有大写字母,中段的判定则与小写字母一并进行。

        总而言之,大写字母不能失配,小写字母按序配完即可。

4.  添加与搜索单词 - 数据结构设计

要求:设计一个数据结构,支持「添加新单词」和「查找字符串是否与任何先前添加的字符串匹配」。

实现词典类 WordDictionary:

  • WordDictionary() 初始化词典对象。
  • void addWord(word) 将 word 添加到数据结构中,之后可以对它进行匹配
  • bool search(word) 如果数据结构中存在字符串与 word 匹配,则返回 True;否则,返回 Falseword 中可能包含一些 .,每个 . 都可以表示任何一个字母。

说明

  • 1 <= word.length <= 25
  • addWord 中的 word 由小写英文字母组成
  • search 中的 word 由 '.' 或小写英文字母组成
  • 最多调用 10e4 次 addWord 和 search
class WordDictionary:

    # 初始化:词长属性,检索记录
	def __init__(self):
		self.s, self.c = {}, {} # 集合:防止重复插入

    # 加词方法:将相同长度的单词放入同一节点
	def addWord(self, word):
        # 键为词长
		n = len(word)
        # 值为单词集合
		if n not in self.s: self.s[n] = set()
		self.s[n].add(word)
        # 清空记录
		self.c.clear()


	def search(self, word):
        # 当前检索单词无记录:添加记录,默认为False
		if word not in self.c:
			self.c[word] = False
            # 以词长为关键字查找
			n = len(word)
			if n in self.s:
                # 遍历当前词长的子节点,即单词集合,逐个取出单词
				for s in self.s[n]:
                    # 匹配标志
					ok = True
                    # 失配:出现'.'则跳过
					for i in range(n):
						if word[i] != '.' and s[i] != word[i]: ok = False; break
                    # 可配:修改记录
					if ok:
						self.c[word] = True
						break
        # 返回结果
        '''
        若为连续检索操作,可直接调用记录返回
        若全部失配,返回默认False
        有可配词,返回True
        '''
		return self.c[word]

         由于搜索词中存在缺失字母,所以以字母为结点建立多叉树已经不再合适,因为多个节点的缺失意味着多枝检索,即多层遍历,字典树的单一搜索路径优势,即单次遍历优势,已经失效了。

        此解法中,将树的深度设计为两层,在节点中直接存储单词,将词长作为哈希值,用集合结构排重,用词典的检索记录减少重复检索的运算量。

        缩进错误详见:http://t.csdnimg.cn/ZcQZ5

5. 单词替换

描述:给定一个由许多词根组成的字典列表 dictionary,以及一个句子字符串 sentence

要求:将句子中有词根的单词用词根替换掉。如果单词有很多词根,则用最短的词根替换掉他。最后输出替换之后的句子。

说明

  • 1 <= dictionary.length <= 1000
  • 1 <= dictionary[i].length <= 100
  • dictionary[i] 仅由小写字母组成。
  • 1 <= sentence.length <= 10e6
  • sentence 仅由小写字母和空格组成。
  • sentence 中单词的总量在范围 [1, 1000] 内。
  • sentence 中每个单词的长度在范围 [1, 1000] 内。
  • sentence 中单词之间由一个空格隔开。
  • sentence 没有前导或尾随空格。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值