Task02

Task02 字符串与字符串匹配(3-7天)

2.1字符串基础知识

  • 字符串(String):简称为串,是由零个或多个字符组成的有限序列。

Python 语言中使用 str 对象来代表字符串。str 对象一种不可变类型对象。即 str 类型创建的字符串对象在定义之后,无法更改字符串的长度,也无法改变或删除字符串中的字符。
https://www.runoob.com/python3/python3-string.html

  • 比较操作:strcmp 方法
def strcmp(str1, str2):
    index1, index2 = 0, 0
    while index1 < len(str1) and index2 < len(str2):
        if ord(str1[index1]) == ord(str2[index2]):
            index1 += 1
            index2 += 1
        elif ord(str1[index1]) < ord(str2[index2]):
            return -1
        else:
            return 1
    
    if len(str1) < len(str2):
        return -1
    elif len(str1) > len(str2):
        return 1
    else:
        return 0

  • 字符编码:ASCII 编码、Unicode 编码 – UTF-8 编码

  • 存储结构:

(1)顺序存储:地址连续;固定长度-定长数组;下标索引[0,len-1];支持随机访

(2)链式存储:线性链表;链节点(data,next);

  • 字符串匹配问题:又称模式匹配(Pattern Matching)。可以简单理解为,给定字符串 T 和 p,在主串 T 中寻找子串 p。主串 T 又被称为文本串,子串 p 又被称为模式串(Pattern)。

(1)单模式串匹配问题:从文本串T找出特定模式串p的所有出现位置。

基于前缀搜索方法:Knuth-Morris-Pratt (KMP) 算法、Shift-Or 算法

基于后缀搜索方法:Boyer-Moore 算法、Horspool 算法、Sunday (Boyer-Moore 算法的简化)

基于子串搜索方法:Rabin-Karp 算法、Backward Dawg Matching (BDM) 算法、Backward Nondeterministtic Dawg Matching (BNDM) 算法和 Backward Oracle Matching (BOM) 算法使用的就是这种思想。其中,Rabin-Karp 算法使用了基于散列的子串搜索算法。

(2)多模式串匹配问题:要求从文本串T中找到模式串集合P中所有模式串pi的所有出现位置。【字典树(Trie Tree)】

基于前缀搜索方法:Aho-Corasick Automaton (AC 自动机) 算法、Multiple Shift-And 算法

基于后缀搜索方法:Commentz-Walter 算法(Boyer-Moore 算法的扩展算法)、Set Horspool 算法(Commentz-Walter 算法的简化算法)、Wu-Manber 算法

基于子串搜索方法:Multiple BNDM 算法、Set Backward Dawg Matching (SBDM) 算法、Set Backwrad Oracle Matching (SBOM) 算法

2.2 练习题目(day3)

1.验证回文串

如果在将所有大写字符转换为小写字符、并移除所有非字母数字字符之后,短语正着读和反着读都一样。则可以认为该短语是一个 回文串 。

字母和数字都属于字母数字字符。

给你一个字符串 s,如果它是 回文串 ,返回 true ;否则,返回 false 。

思路一:设置两个指针left、right,分别指向字符串开头和结尾,从两侧向中间逐个比较。先判断是否为字母或数字,不是则跳过;两者都是则进行比较,相等则移动,不相等则返回False;当left>=right时停止,返回True。

string.lower():转换字符串中所有大写字符为小写。https://www.runoob.com/python3/python3-string-lower.html

string.isalnum():如果字符串至少有一个字符并且所有字符都是字母或数字则返回 True,否则返回 False。https://www.runoob.com/python/att-string-isalnum.html

class Solution:
    def isPalindrome(self, s: str) -> bool:
        left = 0
        right = len(s) - 1
        while left < right:
            while left < right and not s[left].isalnum():
                left += 1
            while left < right and not s[right].isalnum():
                right -= 1
            if left < right:
                if s[left].lower() != s[right].lower():
                    return False
                left += 1
                right -= 1
        return True

思路二:先对字符串进行处理,将所有大写字符转换为小写字符、并提取字母数字字符,再进行比较

s[::-1]:按相反的顺序输出列表的值。https://www.runoob.com/python/python-exercise-example32.html

正则表达式:可使用正则匹配模块re进行字符串处理https://blog.csdn.net/BurningSilence/article/details/118488543

class Solution:
    def isPalindrome(self, s: str) -> bool:
        s = s.lower()
        new = ''
        for i in s:
            if 'a' <= i <= 'z' or '0' <= i <= '9':
                new += i
        return new == new[::-1]

2.反转字符串

编写一个函数,其作用是将输入的字符串反转过来。输入字符串以字符数组 s 的形式给出。

不要给另外的数组分配额外的空间,你必须原地修改输入数组、使用 O(1) 的额外空间解决这一问题。

思路一:设置两个指针l和r分别指向列表首尾,从两侧向中间逐个交换,直到指针相遇

思路二:直接调用库函数

s[:]=s[::-1]表示将原数组反转后赋值给s中每一个对应的位置

s=s[::-1]表示将s反转后赋值给新的对象s(可以通过id函数查看内存地址),与题意原地修改不符。

list.reverse():反向列表中元素

from typing import List
class Solution:
    def reverseString(self, s: List[str]) -> None:
        left = 0
        right = len(s) - 1
        while left < right:
            s[left], s[right] = s[right], s[left]
            left += 1
            right -= 1
class Solution:
    def reverseString(self, s: List[str]) -> None:
        s[:] = s[::-1]
        #s = s.reverse()

3.反转字符串中的单词 III

给定一个字符串 s ,你需要反转字符串中每个单词的字符顺序,同时仍保留空格和单词的初始顺序。

小白思路:双指针left、right指向开头元素,left不动,right向右查找,遇到空格停止,把指针中间字符反转,left=right,继续查找,直到right到达字符串末尾。

class Solution:
    def reverseWords(self, s: str) -> str:
        left,right = 0 , 0
        new = ''
        for i in s:
            if s[right] != ' ':
                right += 1
            else:
                ss = s[left:right]
                ss = ss[::-1]
                new += ss
                right += 1
                left = right
                new += ' '
        ss = s[left:right]
        ss = ss[::-1]
        new += ss
        return new

思路二:调用函数,将字符串分割成单词列表,然后把每个单词反转切片

split() :通过指定分隔符对字符串进行切片,该方法将字符串分割成子字符串并返回一个由这些子字符串组成的列表。
https://www.runoob.com/python3/python3-string-split.html

join():将序列中的元素以指定的字符连接生成一个新的字符串。
https://www.runoob.com/python3/python3-string-join.html

class Solution(object):
    def reverseWords(self, s):
        return " ".join(word[::-1] for word in s.split(" "))

思路三:两次切片,不需要遍历

class Solution(object):
    def reverseWords(self, s):
        return " ".join(s.split(" ")[::-1])[::-1]   #先反转单词列表 再反转字符串
       #return " ".join(s[::-1].split(" ")[::-1])   #先反转字符串,再反转单词列表

作者:Swants
链接:https://leetcode.cn/problems/reverse-words-in-a-string-iii/solutions/262135/python-fan-zhuan-zi-fu-chuan-zhong-dan-ci-si-lu-xi/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  Input In [19]
    作者:Swants
      ^
SyntaxError: invalid character ':' (U+FF1A)

2.3 Brute Force 算法(BF-暴力匹配算法)

BF 算法思想:对于给定文本串 T 与模式串 p,从文本串的第一个字符开始与模式串 p 的第一个字符进行比较,如果相等,则继续逐个比较后续字符,否则从文本串 T 的第二个字符起重新和模式串 p 进行比较。依次类推,直到模式串 p 中每个字符依次与文本串 T 的一个连续子串相等,则模式匹配成功。否则模式匹配失败。

def bruteForce(T: str, p: str) -> int:
    n, m = len(T), len(p)
    
    i, j = 0, 0                     # i 表示文本串 T 的当前位置,j 表示模式串 p 的当前位置
    while i < n and j < m:          # i 或 j 其中一个到达尾部时停止搜索
        if T[i] == p[j]:            # 如果相等,则继续进行下一个字符匹配
            i += 1
            j += 1
        else:
            i = i - (j - 1)         # 如果匹配失败则将 i 移动到上次匹配开始位置的下一个位置
            j = 0                   # 匹配失败 j 回退到模式串开始位置

    if j == m:
        return i - j                # 匹配成功,返回匹配的开始位置
    else:
        return -1                   # 匹配失败,返回 -1

2.4 Rabin Karp 算法(RK)

Rabin Karp 算法思想:对于给定文本串 T 与模式串 p,通过滚动哈希算快速筛选出与模式串 p 不匹配的文本位置,然后在其余位置继续检查匹配项。

「滚动哈希算法」:用了子串中每一位字符的哈希值,并且还可以根据上一个子串的哈希值,快速计算相邻子串的哈希值,从而使得每次计算子串哈希值的时间复杂度降为了O(1)。

# T 为文本串,p 为模式串,d 为字符集的字符种类数,q 为质数
def rabinKarp(T: str, p: str, d, q) -> int:
    n, m = len(T), len(p)
    if n < m:
        return -1
    
    hash_p, hash_t = 0, 0
    
    for i in range(m):
        hash_p = (hash_p * d + ord(p[i])) % q           # 计算模式串 p 的哈希值
        hash_t = (hash_t * d + ord(T[i])) % q           # 计算文本串 T 中第一个子串的哈希值
    
    power = pow(d, m - 1) % q                           # power 用于移除字符哈希时
    
    for i in range(n - m + 1):
        if hash_p == hash_t:                            # 检查模式串 p 的哈希值和子串的哈希值
            match = True                                # 如果哈希值相等,验证模式串和子串每个字符是否完全相同(避免哈希冲突)
            for j in range(m):
                if T[i + j] != p[j]:
                    match = False                       # 模式串和子串某个字符不相等,验证失败,跳出循环
                    break
            if match:                                   # 如果模式串和子串每个字符是否完全相同,返回匹配开始位置
                return i
        if i < n - m:                                   # 计算下一个相邻子串的哈希值
            hash_t = (hash_t - power * ord(T[i])) % q   # 移除字符 T[i]
            hash_t = (hash_t * d + ord(T[i + m])) % q   # 增加字符 T[i + m]
            hash_t = (hash_t + q) % q                   # 确保 hash_t >= 0
        
    return -1

2.5 KMP 算法

KMP 算法思想:对于给定文本串 T 与模式串 p,当发现文本串 T 的某个字符与模式串 p 不匹配的时候,可以利用匹配失败后的信息,尽量减少模式串与文本串的匹配次数,避免文本串位置的回退,以达到快速匹配的目的。

对模式串 p 进行了预处理,计算出一个 「部分匹配表」,用一个数组 next 来记录。然后在每次失配发生时,不回退文本串的指针 i,而是根据「部分匹配表」中模式串失配位置 j 的前一个位置的值,即 next[j - 1] 的值来决定模式串可以向右移动的位数。

next数组:next[j] 表示的含义是–记录下标 j 之前(包括 j)的模式串 p 中,最长相等前后缀的长度。以通过递推的方式构造 next 数组。

# 生成 next 数组
# next[j] 表示下标 j 之前的模式串 p 中,最长相等前后缀的长度
def generateNext(p: str):
    m = len(p)
    next = [0 for _ in range(m)]                # 初始化数组元素全部为 0
    
    left = 0                                    # left 表示前缀串开始所在的下标位置
    for right in range(1, m):                   # right 表示后缀串开始所在的下标位置
        while left > 0 and p[left] != p[right]: # 匹配不成功, left 进行回退, left == 0 时停止回退
            left = next[left - 1]               # left 进行回退操作
        if p[left] == p[right]:                 # 匹配成功,找到相同的前后缀,先让 left += 1,此时 left 为前缀长度
            left += 1
        next[right] = left                      # 记录前缀长度,更新 next[right], 结束本次循环, right += 1

    return next

# KMP 匹配算法,T 为文本串,p 为模式串
def kmp(T: str, p: str) -> int:
    n, m = len(T), len(p)
    
    next = generateNext(p)                      # 生成 next 数组
    
    j = 0                                       # j 为模式串中当前匹配的位置
    for i in range(n):                          # i 为文本串中当前匹配的位置
        while j > 0 and T[i] != p[j]:           # 如果模式串前缀匹配不成功, 将模式串进行回退, j == 0 时停止回退
            j = next[j - 1]
        if T[i] == p[j]:                        # 当前模式串前缀匹配成功,令 j += 1,继续匹配
            j += 1
        if j == m:                              # 当前模式串完全匹配成功,返回匹配开始位置
            return i - j + 1
    return -1                                   # 匹配失败,返回 -1
            
print(kmp("abbcfdddbddcaddebc", "ABCABCD"))
print(kmp("abbcfdddbddcaddebc", "bcf"))
print(kmp("aaaaa", "bba"))
print(kmp("mississippi", "issi"))
print(kmp("ababbbbaaabbbaaa", "bbbb"))

-1
2
-1
1
3

2.6练习(day4)

1.找出字符串中第一个匹配项的下标

给你两个字符串 haystack 和 needle ,请你在 haystack 字符串中找出 needle 字符串的第一个匹配项的下标(下标从 0 开始)。如果 needle 不是 haystack 的一部分,则返回 -1 。

思路:可直接使用前边三种算法;可以调用find()函数https://www.runoob.com/python3/python3-string-find.html

class Solution:
    def strStr(self, haystack: str, needle: str) -> int:
        return haystack.find(needle)

2.重复的子字符串

给定一个非空的字符串 s ,检查是否可以通过由它的一个子串重复多次构成。

思路一:KMP算法,已经开晕了,直接复制官方题解
思路二:如果将两个 ss 连在一起,并移除第一个和最后一个字符,那么得到的字符串一定包含 s,即 s 是它的一个子串。

class Solution:
    def repeatedSubstringPattern(self, s: str) -> bool:
        def kmp(query: str, pattern: str) -> bool:
            n, m = len(query), len(pattern)
            fail = [-1] * m
            for i in range(1, m):
                j = fail[i - 1]
                while j != -1 and pattern[j + 1] != pattern[i]:
                    j = fail[j]
                if pattern[j + 1] == pattern[i]:
                    fail[i] = j + 1
            match = -1
            for i in range(1, n - 1):
                while match != -1 and pattern[match + 1] != query[i]:
                    match = fail[match]
                if pattern[match + 1] == query[i]:
                    match += 1
                    if match == m - 1:
                        return True
            return False
        
        return kmp(s + s, s)

作者:力扣官方题解
链接:https://leetcode.cn/problems/repeated-substring-pattern/solutions/386481/zhong-fu-de-zi-zi-fu-chuan-by-leetcode-solution/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
class Solution:
    def repeatedSubstringPattern(self, s: str) -> bool:
        return (s + s).find(s, 1) != len(s)

3.重复叠加字符串匹配

给定两个字符串 a 和 b,寻找重复叠加字符串 a 的最小次数,使得字符串 b 成为叠加后的字符串 a 的子串,如果不存在则返回 -1。

注意:字符串 “abc” 重复叠加 0 次是 “”,重复叠加 1 次是 “abc”,重复叠加 2 次是 “abcabc”。

思路:覆盖b字符串最少需要b/a个a字符串,最多需要b/a +2个字符串

class Solution:
    def repeatedStringMatch(self, a: str, b: str) -> int:
        j = len(b) // len(a)
        k = j+3
        s = a*j
        for i in range(j,k):
            if b in s:
                return i
            s += a
        return -1

2.7练习 (day5)

1.旋转字符串

给定两个字符串, s 和 goal。如果在若干次旋转操作之后,s 能变成 goal ,那么返回 true 。
s 的 旋转操作 就是将 s 最左边的字符移动到最右边。
例如, 若 s = ‘abcde’,在旋转一次之后结果就是’bcdea’ 。

思路:判断字符长度是否相等,长度不相等返回F,长度相等则判断goal是否在s+s中。

class Solution:
    def rotateString(self, s: str, goal: str) -> bool:
        if len(s) == len(goal):
            return goal in s+s
        return False

2.数组中的字符串匹配

给你一个字符串数组 words ,数组中的每个字符串都可以看作是一个单词。请你按 任意 顺序返回 words 中是其他单词的子字符串的所有单词。

如果你可以删除 words[j] 最左侧和/或最右侧的若干字符得到 words[i] ,那么字符串 words[i] 就是 words[j] 的一个子字符串。

思路一:暴力求解,双循环遍历数组中的每一个字符串是否是其他字符串的子串,是则添加到结果数组。

class Solution:
    def stringMatching(self, words: List[str]) -> List[str]:
        res=[]
        for i in range(len(words)):
            for j in range(len(words)):
                if i!=j and words[i] in words[j]:
                    res.append(words[i])
                    break
        return res

思路二:利用python内置函数count解决该题 该函数返回字符串出现子串的次数,如果次数不为1,则为子字符串

count() :用于统计字符串里某个字符出现的次数。可选参数为在字符串搜索的开始与结束位置。
https://www.runoob.com/python3/python3-string-count.html

class Solution(object):
    def stringMatching(self, words: List[str]) -> List[str]:
        als=','.join(words)
        res=[]
        for s in words:
            if als.count(s)!=1:
                res.append(s)
        return res

3.查找给定哈希值的子串

给定整数 p 和 m ,一个长度为 k 且下标从 0 开始的字符串 s 的哈希值按照如下函数计算:

hash(s, p, m) = (val(s[0]) * p0 + val(s[1]) * p1 + … + val(s[k-1]) * pk-1) mod m.
其中 val(s[i]) 表示 s[i] 在字母表中的下标,从 val(‘a’) = 1 到 val(‘z’) = 26 。

给你一个字符串 s 和整数 power,modulo,k 和 hashValue 。请你返回 s 中 第一个 长度为 k 的 子串 sub ,满足 hash(sub, power, modulo) == hashValue 。

测试数据保证一定 存在 至少一个这样的子串。

子串 定义为一个字符串中连续非空字符组成的序列。

思路:

2.8 字典树

  • 字典树(Trie):

又称为前缀树、单词查找树,是一种树形结构。顾名思义,就是一个像字典一样的树。它是字典的一种存储方式。字典中的每个单词在字典树中表现为一条从根节点出发的路径,路径相连的边上的字母连起来就形成对应的字符串。

字典树设计的核心思想:利用空间换时间,利用字符串的公共前缀来降低查询时间的开销,最大限度的减少无谓的字符串比较,以达到提高效率的目的。

  • 字典树的节点结构:

如果字符串所涉及的字符集合只包含小写英文字母的话,我们可以使用一个长度为 26 的数组来表示当前节点的多个子节点。

如果所涉及的字符集合不仅包含小写字母,还包含大写字母和其他字符,我们可以使用哈希表来表示当前节点的多个子节点。

  • 字典树的基本结构:

在字典树的初始化操作时,定义一个根节点。并且这个根节点不用保存字符。在后续进行插入操作、查找操作都是从字典树的根节点开始的。

  • 字典树的插入操作:

依次遍历单词中的字符 ch,并从字典树的根节点的子节点位置开始进行插入操作(根节点不包含字符)。
如果当前节点的子节点中,不存在键为 ch 的节点,则建立一个节点,并将其保存到当前节点的子节点中,即 cur.children[ch] = Node(),然后令当前节点指向新建立的节点,然后继续处理下一个字符。
如果当前节点的子节点中,存在键为 ch 的节点,则直接令当前节点指向键为 ch 的节点,继续处理下一个字符。
在单词处理完成时,将当前节点标记为单词结束。

  • 字典树的创建操作:

首先初始化一个字典树,即 trie = Trie()。
然后依次遍历字符串中的所有单词,将其一一插入到字典树中。

  • 字典树的查找单词操作:

依次遍历单词中的字符,并从字典树的根节点位置开始进行查找操作。
如果当前节点的子节点中,不存在键为 ch 的节点,则说明不存在该单词,直接返回 False。
如果当前节点的子节点中,存在键为 ch 的节点,则令当前节点指向新建立的节点,然后继续查找下一个字符。
在单词处理完成时,判断当前节点是否有单词结束标记,如果有,则说明字典树中存在该单词,返回 True。否则,则说明字典树中不存在该单词,返回 False。

  • 字典树的查找前缀操作:

在字典树中查找某个前缀是否存在,和字典树的查找单词操作一样,不同点在于最后不需要判断是否有单词结束标记。

class Node:                                     # 字符节点
    def __init__(self):                         # 初始化字符节点
        self.children = dict()                  # 初始化子节点
        self.isEnd = False                      # isEnd 用于标记单词结束
        
        
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.children:          # 如果当前节点的子节点中,不存在键为 ch 的节点
                cur.children[ch] = Node()       # 建立一个节点,并将其保存到当前节点的子节点
            cur = cur.children[ch]              # 令当前节点指向新建立的节点,继续处理下一个字符
        cur.isEnd = True                        # 单词处理完成时,将当前节点标记为单词结束

    # 查找字典树中是否存在一个单词
    def search(self, word: str) -> bool:
        cur = self.root
        for ch in word:                         # 遍历单词中的字符
            if ch not in cur.children:          # 如果当前节点的子节点中,不存在键为 ch 的节点
                return False                    # 直接返回 False
            cur = cur.children[ch]              # 令当前节点指向新建立的节点,然后继续查找下一个字符

        return cur is not None and cur.isEnd    # 判断当前节点是否为空,并且是否有单词结束标记

    # 查找字典树中是否存在一个前缀
    def startsWith(self, prefix: str) -> bool:
        cur = self.root
        for ch in prefix:                       # 遍历前缀中的字符
            if ch not in cur.children:          # 如果当前节点的子节点中,不存在键为 ch 的节点
                return False                    # 直接返回 False
            cur = cur.children[ch]              # 令当前节点指向新建立的节点,然后继续查找下一个字符
        return cur is not None                  # 判断当前节点是否为空,不为空则查找成功

  • 字典树的应用:

字符串检索:事先将已知的⼀些字符串(字典)的有关信息存储到字典树⾥, 查找⼀些字符串是否出现过、出现的频率。

前缀统计:统计⼀个串所有前缀单词的个数,只需统计从根节点到叶子节点路径上单词出现的个数,也可以判断⼀个单词是否为另⼀个单词的前缀。

最长公共前缀问题:利用字典树求解多个字符串的最长公共前缀问题。将⼤量字符串都存储到⼀棵字典树上时, 可以快速得到某些字符串的公共前缀。对所有字符串都建⽴字典树,两个串的最长公共前缀的长度就是它们所在节点最近公共祖先的长度,于是转变为最近公共祖先问题。

字符串排序:利⽤字典树进⾏串排序。例如,给定多个互不相同的仅由⼀个单词构成的英⽂名,将它们按字典序从⼩到⼤输出。采⽤数组⽅式创建字典树,字典树中每个节点的所有⼦节点都是按照其字母⼤⼩排序的。然后对字典树进⾏先序遍历,输出的相应字符串就是按字典序排序的结果。

2.9 练习(day6)

1.实现 Trie (前缀树)

Trie(发音类似 “try”)或者说 前缀树 是一种树形数据结构,用于高效地存储和检索字符串数据集中的键。这一数据结构有相当多的应用情景,例如自动补完和拼写检查。

请你实现 Trie 类:

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

class Trie:

    def __init__(self):                         # 初始化字典树  
        self.children = dict()                  # 初始化子节点
        self.isEnd = False                    # 初始化根节点(根节点不保存字符)

    # 向字典树中插入一个单词
    def insert(self, word: str) -> None:
        cur = self
        for ch in word:                         # 遍历单词中的字符
            if ch not in cur.children:          # 如果当前节点的子节点中,不存在键为 ch 的节点
                cur.children[ch] = Trie()       # 建立一个节点,并将其保存到当前节点的子节点
            cur = cur.children[ch]              # 令当前节点指向新建立的节点,继续处理下一个字符
        cur.isEnd = True                        # 单词处理完成时,将当前节点标记为单词结束

    # 查找字典树中是否存在一个单词
    def search(self, word: str) -> bool:
        cur = self
        for ch in word:                         # 遍历单词中的字符
            if ch not in cur.children:          # 如果当前节点的子节点中,不存在键为 ch 的节点
                return False                    # 直接返回 False
            cur = cur.children[ch]              # 令当前节点指向新建立的节点,然后继续查找下一个字符

        return cur is not None and cur.isEnd    # 判断当前节点是否为空,并且是否有单词结束标记

    # 查找字典树中是否存在一个前缀
    def startsWith(self, prefix: str) -> bool:
        cur = self
        for ch in prefix:                       # 遍历前缀中的字符
            if ch not in cur.children:          # 如果当前节点的子节点中,不存在键为 ch 的节点
                return False                    # 直接返回 False
            cur = cur.children[ch]              # 令当前节点指向新建立的节点,然后继续查找下一个字符
        return cur is not None                  # 判断当前节点是否为空,不为空则查找成功

2.键值映射

设计一个 map ,满足以下几点:

字符串表示键,整数表示值
返回具有前缀等于给定字符串的键的值的总和
实现一个 MapSum 类:

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

class TrieNode:
    def __init__(self):
        self.val = 0
        self.next = [None for _ in range(26)]

class MapSum:
    def __init__(self):
        self.root = TrieNode()
        self.map = {}

    def insert(self, key: str, val: int) -> None:
        delta = val
        if key in self.map:
            delta -= self.map[key]
        self.map[key] = val
        node = self.root
        for c in key:
            if node.next[ord(c) - ord('a')] is None:
                node.next[ord(c) - ord('a')] = TrieNode()
            node = node.next[ord(c) - ord('a')]
            node.val += delta

    def sum(self, prefix: str) -> int:
        node = self.root
        for c in prefix:
            if node.next[ord(c) - ord('a')] is None:
                return 0            
            node = node.next[ord(c) - ord('a')]
        return node.val

作者:力扣官方题解
链接:https://leetcode.cn/problems/map-sum-pairs/solutions/1098992/jian-zhi-ying-she-by-leetcode-solution-j4xy/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  Input In [15]
    作者:力扣官方题解
      ^
SyntaxError: invalid character ':' (U+FF1A)

3.驼峰式匹配

给你一个字符串数组 queries,和一个表示模式的字符串 pattern,请你返回一个布尔数组 answer 。只有在待查项 queries[i] 与模式串 pattern 匹配时, answer[i] 才为 true,否则为 false。

如果可以将小写字母插入模式串 pattern 得到待查询项 query,那么待查询项与给定模式串匹配。可以在任何位置插入每个字符,也可以不插入字符。

思路:双指针匹配

class Solution:
    def camelMatch(self, queries: List[str], pattern: str) -> List[bool]:
        def check(s, t):
            m, n = len(s), len(t)
            i = j = 0
            while j < n:
                while i < m and s[i] != t[j] and s[i].islower():
                    i += 1
                if i == m or s[i] != t[j]:
                    return False
                i, j = i + 1, j + 1
            while i < m and s[i].islower():
                i += 1
            return i == m

        return [check(q, pattern) for q in queries]

2.10 练习(day7)

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

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

实现词典类 WordDictionary :

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

class TrieNode:
    def __init__(self):
        self.children = [None] * 26
        self.isEnd = 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] = TrieNode()
            node = node.children[ch]
        node.isEnd = True


class WordDictionary:
    def __init__(self):
        self.trieRoot = TrieNode()

    def addWord(self, word: str) -> None:
        self.trieRoot.insert(word)

    def search(self, word: str) -> bool:
        def dfs(index: int, node: TrieNode) -> bool:
            if index == len(word):
                return node.isEnd
            ch = word[index]
            if ch != '.':
                child = node.children[ord(ch) - ord('a')]
                if child is not None and dfs(index + 1, child):
                    return True
            else:
                for child in node.children:
                    if child is not None and dfs(index + 1, child):
                        return True
            return False

        return dfs(0, self.trieRoot)

作者:力扣官方题解
链接:https://leetcode.cn/problems/design-add-and-search-words-data-structure/solutions/1053383/tian-jia-yu-sou-suo-dan-ci-shu-ju-jie-go-n4ud/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  Input In [20]
    作者:力扣官方题解
      ^
SyntaxError: invalid character ':' (U+FF1A)

2.单词替换

在英语中,我们有一个叫做 词根(root) 的概念,可以词根后面添加其他一些词组成另一个较长的单词——我们称这个词为 继承词(successor)。例如,词根an,跟随着单词 other(其他),可以形成新的单词 another(另一个)。

现在,给定一个由许多词根组成的词典 dictionary 和一个用空格分隔单词形成的句子 sentence。你需要将句子中的所有继承词用词根替换掉。如果继承词有许多可以形成它的词根,则用最短的词根替换它。

你需要输出替换之后的句子。

class Solution:
    def replaceWords(self, dictionary: List[str], sentence: str) -> str:
        dictionarySet = set(dictionary)
        words = sentence.split(' ')
        for i, word in enumerate(words):
            for j in range(1, len(words) + 1):
                if word[:j] in dictionarySet:
                    words[i] = word[:j]
                    break
        return ' '.join(words)

作者:力扣官方题解
链接:https://leetcode.cn/problems/replace-words/solutions/1649109/dan-ci-ti-huan-by-leetcode-solution-pl6v/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  Input In [21]
    作者:力扣官方题解
      ^
SyntaxError: invalid character ':' (U+FF1A)

3.实现一个魔法字典

设计一个使用单词列表进行初始化的数据结构,单词列表中的单词 互不相同 。 如果给出一个单词,请判定能否只将这个单词中一个字母换成另一个字母,使得所形成的新单词存在于你构建的字典中。

实现 MagicDictionary 类:

MagicDictionary() 初始化对象
void buildDict(String[] dictionary) 使用字符串数组 dictionary 设定该数据结构,dictionary 中的字符串互不相同
bool search(String searchWord) 给定一个字符串 searchWord ,判定能否只将字符串中 一个 字母换成另一个字母,使得所形成的新字符串能够与字典中的任一字符串匹配。如果可以,返回 true ;否则,返回 false 。

class Trie:
    def __init__(self):
        self.is_finished = False
        self.child = dict()


class MagicDictionary:

    def __init__(self):
        self.root = Trie()

    def buildDict(self, dictionary: List[str]) -> None:
        for word in dictionary:
            cur = self.root
            for ch in word:
                if ch not in cur.child:
                    cur.child[ch] = Trie()
                cur = cur.child[ch]
            cur.is_finished = True

    def search(self, searchWord: str) -> bool:
        def dfs(node: Trie, pos: int, modified: bool) -> bool:
            if pos == len(searchWord):
                return modified and node.is_finished
            
            ch = searchWord[pos]
            if ch in node.child:
                if dfs(node.child[ch], pos + 1, modified):
                    return True
                
            if not modified:
                for cnext in node.child:
                    if ch != cnext:
                        if dfs(node.child[cnext], pos + 1, True):
                            return True
            
            return False
        
        return dfs(self.root, 0, False)

作者:力扣官方题解
链接:https://leetcode.cn/problems/implement-magic-dictionary/solutions/1656423/shi-xian-yi-ge-mo-fa-zi-dian-by-leetcode-b35s/
来源:力扣(LeetCode)
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
  Input In [19]
    作者:力扣官方题解
      ^
SyntaxError: invalid character ':' (U+FF1A)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值