学习网址:https://datawhalechina.github.io/leetcode-notes/#/ch03/index.md
DAY5
给定两个字符串, s
和 goal
。如果在若干次旋转操作之后,s
能变成 goal
,那么返回 true
。
s
的 旋转操作 就是将 s
最左边的字符移动到最右边。
- 例如, 若
s = 'abcde'
,在旋转一次之后结果就是'bcdea'
。
示例 1:
输入: s = "abcde", goal = "cdeab" 输出: true
示例 2:
输入: s = "abcde", goal = "abced" 输出: false
提示:
1 <= s.length, goal.length <= 100
s
和goal
由小写英文字母组成class Solution(object): def rotateString(self, s, goal): """ :type s: str :type goal: str :rtype: bool """ if len(s) != len(goal): # 如果长度不同,直接返回False return False # 最大位移次数 len(s) for _ in range(len(s)): if s == goal: return True else: # 将s最左边的字符移动到最右边 s = s[1:] + s[0] # 如果循环结束都没有找到匹配的字符串,返回False return False
给你一个字符串数组
words
,数组中的每个字符串都可以看作是一个单词。请你按 任意 顺序返回words
中是其他单词的子字符串的所有单词。如果你可以删除
words[j]
最左侧和/或最右侧的若干字符得到words[i]
,那么字符串words[i]
就是words[j]
的一个子字符串。示例 1:
输入:words = ["mass","as","hero","superhero"] 输出:["as","hero"] 解释:"as" 是 "mass" 的子字符串,"hero" 是 "superhero" 的子字符串。 ["hero","as"] 也是有效的答案。
示例 2:
输入:words = ["leetcode","et","code"] 输出:["et","code"] 解释:"et" 和 "code" 都是 "leetcode" 的子字符串。
示例 3:
输入:words = ["blue","green","bu"] 输出:[]
-
class Solution(object): def subStrHash(self, s, power, modulo, k, hashValue): """ :type s: str :type power: int :type modulo: int :type k: int :type hashValue: int :rtype: str """ mult = 1 # 初始化 power^(k-1) mod modulo hash_ = 0 # 初始化子串哈希值 n = len(s) pos = -1 # 记录第一个符合条件的子串的起始下标 # 预处理计算最后一个子串的哈希值和 power^k mod modulo for i in range(n - 1, n - k - 1, -1): hash_ = (hash_ * power + (ord(s[i]) - ord('a') + 1)) % modulo if i != n - k: mult = (mult * power) % modulo # 如果最后一个子串的哈希值符合条件,记录起始下标 if hash_ == hashValue: pos = n - k # 向前计算哈希值并尝试更新下标 for i in range(n - k - 1, -1, -1): # 更新哈希值 hash_ = ((hash_ - (ord(s[i + k]) - ord('a') + 1) * mult) % modulo + modulo) * power + (ord(s[i]) - ord('a') + 1) hash_ %= modulo if hash_ == hashValue: pos = i # 返回找到的第一个符合条件的子串 return s[pos:pos + k]
给定整数 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
。
测试数据保证一定 存在 至少一个这样的子串。
子串 定义为一个字符串中连续非空字符组成的序列。
示例 1:
输入:s = "leetcode", power = 7, modulo = 20, k = 2, hashValue = 0 输出:"ee" 解释:"ee" 的哈希值为 hash("ee", 7, 20) = (5 * 1 + 5 * 7) mod 20 = 40 mod 20 = 0 。 "ee" 是长度为 2 的第一个哈希值为 0 的子串,所以我们返回 "ee" 。
示例 2:
输入:s = "fbxzaad", power = 31, modulo = 100, k = 3, hashValue = 32 输出:"fbx" 解释:"fbx" 的哈希值为 hash("fbx", 31, 100) = (6 * 1 + 2 * 31 + 24 * 312) mod 100 = 23132 mod 100 = 32 。 "bxz" 的哈希值为 hash("bxz", 31, 100) = (2 * 1 + 24 * 31 + 26 * 312) mod 100 = 25732 mod 100 = 32 。 "fbx" 是长度为 3 的第一个哈希值为 32 的子串,所以我们返回 "fbx" 。 注意,"bxz" 的哈希值也为 32 ,但是它在字符串中比 "fbx" 更晚出现。
class Solution(object):
def subStrHash(self, s, power, modulo, k, hashValue):
"""
:type s: str
:type power: int
:type modulo: int
:type k: int
:type hashValue: int
:rtype: str
"""
mult = 1 # 初始化 power^(k-1) mod modulo
hash_ = 0 # 初始化子串哈希值
n = len(s)
pos = -1 # 记录第一个符合条件的子串的起始下标
# 预处理计算最后一个子串的哈希值和 power^k mod modulo
for i in range(n - 1, n - k - 1, -1):
hash_ = (hash_ * power + (ord(s[i]) - ord('a') + 1)) % modulo
if i != n - k:
mult = (mult * power) % modulo
# 如果最后一个子串的哈希值符合条件,记录起始下标
if hash_ == hashValue:
pos = n - k
# 向前计算哈希值并尝试更新下标
for i in range(n - k - 1, -1, -1):
# 更新哈希值
hash_ = ((hash_ - (ord(s[i + k]) - ord('a') + 1) * mult) % modulo + modulo) * power + (ord(s[i]) - ord('a') + 1)
hash_ %= modulo
if hash_ == hashValue:
pos = i
# 返回找到的第一个符合条件的子串
return s[pos:pos + k]
1.0208. 实现 Trie (前缀树)
1.1 题目大意
要求:实现前缀树数据结构的相关类 Trie
类。
Trie
类:
Trie()
初始化前缀树对象。void insert(String word)
向前缀树中插入字符串word
。boolean search(String word)
如果字符串word
在前缀树中,返回True
(即,在检索之前已经插入);否则,返回False
。boolean startsWith(String prefix)
如果之前已经插入的字符串word
的前缀之一为prefix
,返回True
;否则,返回False
。
# 定义TrieNode类,表示Trie中的一个节点
class TrieNode:
def __init__(self):
# children是一个字典,存储指向子节点的引用
self.children = {}
# end_of_word是一个布尔值,标记是否有单词在这个节点结束
self.end_of_word = False
# 定义Trie类
class Trie:
def __init__(self):
# root是Trie的根节点
self.root = TrieNode()
# insert方法将一个单词插入到Trie中
def insert(self, word):
node = self.root
# 遍历单词的每个字符
for char in word:
# 如果字符不在当前节点的children中,就创建一个新的TrieNode并将其添加到children中
if char not in node.children:
node.children[char] = TrieNode()
# 移动到下一个节点
node = node.children[char]
# 将最后一个节点的end_of_word标记为True,表示一个单词的结束
node.end_of_word = True
# search方法检查一个单词是否在Trie中
def search(self, word):
node = self.root
# 遍历单词的每个字符
for char in word:
# 如果在某个节点找不到对应的字符,就返回False
if char not in node.children:
return False
# 移动到下一个节点
node = node.children[char]
# 如果找到了单词的最后一个字符,并且该节点的end_of_word为True,就返回True,否则返回False
return node.end_of_word
# startsWith方法检查Trie中是否有以给定前缀开头的单词
def startsWith(self, prefix):
node = self.root
# 遍历前缀的每个字符
for char in prefix:
# 如果在某个节点找不到对应的字符,就返回False
if char not in node.children:
return False
# 移动到下一个节点
node = node.children[char]
# 只要找到前缀的最后一个字符就足够了,不需要检查end_of_word
return True
2.0677. 键值映射
2.1 题目大意
要求:实现一个 MapSum 类,支持两个方法,insert
和 sum
:
MapSum()
初始化 MapSum 对象。void insert(String key, int val)
插入key-val
键值对,字符串表示键key
,整数表示值val
。如果键key
已经存在,那么原来的键值对将被替代成新的键值对。int sum(string prefix)
返回所有以该前缀prefix
开头的键key
的值的总和。
class MapSum:
def __init__(self):
self.map = {} # 存储键值对
self.prefix_sum = {} # 存储前缀的和
def insert(self, key, val):
if key in self.map: # 如果键已存在,先减去旧值
self.update_prefix_sum(key, self.map[key], False)
self.map[key] = val # 更新键值对
self.update_prefix_sum(key, val, True) # 更新前缀和
def sum(self, prefix):
return self.prefix_sum.get(prefix, 0) # 返回前缀的和,如果没有则返回0
def update_prefix_sum(self, key, val, is_add):
# 递归更新前缀和
for i in range(1, len(key) + 1):
current_prefix = key[:i]
if is_add:
self.prefix_sum[current_prefix] = self.prefix_sum.get(current_prefix, 0) + val
else:
self.prefix_sum[current_prefix] = self.prefix_sum.get(current_prefix, 0) - val
3.1023. 驼峰式匹配
3.1 题目大意
描述:给定待查询列表 queries
,和模式串 pattern
。如果我们可以将小写字母(0 个或多个)插入模式串 pattern
中间(任意位置)得到待查询项 queries[i]
,那么待查询项与给定模式串匹配。如果匹配,则对应答案为 True
,否则为 False
。
要求:将匹配结果存入由布尔值组成的答案列表中,并返回。
class Solution(object):
def camelMatch(self, queries, pattern):
"""
:type queries: List[str]
:type pattern: str
:rtype: List[bool]
"""
# 字符串数组 queries
# 模式串 pattern
# 如果可以将小写字母插入模式串 pattern 得到待查询项 queries[i],那么待查询项与给定模式串匹配。
def match(query, pattern):
i = j = 0
while i < len(query) and j < len(pattern):
if query[i] == pattern[j]:
j += 1
elif query[i].isupper():
return False
i += 1
return j == len(pattern) and all(c.islower() for c in query[i:])
return [match(query, pattern) for query in queries]
1.0211. 添加与搜索单词 - 数据结构设计
1.1 题目大意
要求:设计一个数据结构,支持「添加新单词」和「查找字符串是否与任何先前添加的字符串匹配」。
实现词典类 WordDictionary:
WordDictionary()
初始化词典对象。void addWord(word)
将word
添加到数据结构中,之后可以对它进行匹配bool search(word)
如果数据结构中存在字符串与word
匹配,则返回True
;否则,返回False
。word
中可能包含一些.
,每个.
都可以表示任何一个字母。
class TrieNode:
# 定义 Trie 节点类
def __init__(self):
# 子节点字典,键为字符,值为 TrieNode 对象
self.children = {}
# 标记该节点是否为一个完整单词的结束
self.is_word = False
class WordDictionary:
# 初始化词典对象
def __init__(self):
self.root = TrieNode()
# 向词典中添加单词
def addWord(self, word):
node = self.root
# 遍历单词的每个字符
for char in word:
# 如果字符不在当前节点的子节点中,则创建一个新的 TrieNode
if char not in node.children:
node.children[char] = TrieNode()
# 移动到下一个节点
node = node.children[char]
# 当所有字符遍历完后,标记最后一个节点为一个完整单词
node.is_word = True
# 搜索函数,判断给定单词是否在词典中或与词典中的单词匹配
def search(self, word):
# 调用辅助函数进行深度优先搜索
return self._search_helper(self.root, word, 0)
# 辅助搜索函数,采用递归进行深度优先搜索
def _search_helper(self, node, word, index):
# 当已经遍历完给定单词的所有字符时
if index == len(word):
# 如果当前节点是一个完整单词的结束,则匹配成功
return node.is_word
char = word[index]
# 如果当前字符不是通配符
if char != '.':
# 如果字符存在于当前节点的子节点中,则继续搜索
if char in node.children:
return self._search_helper(node.children[char], word, index + 1)
else:
# 否则,匹配失败
return False
else:
# 当前字符是通配符,需要遍历当前节点的所有子节点进行搜索
for child in node.children.values():
# 如果在任意子节点中找到了匹配,则返回 True
if self._search_helper(child, word, index + 1):
return True
# 所有子节点都未找到匹配,则匹配失败
return False
2.0648. 单词替换
2.1 题目大意
描述:给定一个由许多词根组成的字典列表 dictionary
,以及一个句子字符串 sentence
。
要求:将句子中有词根的单词用词根替换掉。如果单词有很多词根,则用最短的词根替换掉他。最后输出替换之后的句子。
class Solution(object):
def replaceWords(self, dictionary, sentence):
"""
:type dictionary: List[str]
:type sentence: str
:rtype: str
"""
# 词根 + 继承词 = 衍生词
# 对词典中的词按照长度排序
dictionary.sort(key=lambda x: len(x))
# 分割句子为单词列表
words = sentence.split()
# 遍历每个单词
for i in range(len(words)):
# 遍历词典中的每个词根
for root in dictionary:
# 如果单词以词根开头,就用词根替换这个单词
if words[i].startswith(root):
words[i] = root
break
# 将处理后的单词列表重新组合成句子
return ' '.join(words)
3.0676. 实现一个魔法字典
3.1 题目大意
要求:设计一个使用单词表进行初始化的数据结构。单词表中的单词互不相同。如果给出一个单词,要求判定能否将该单词中的一个字母替换成另一个字母,是的所形成的新单词已经在够构建的单词表中。
实现 MagicDictionary 类:
MagicDictionary()
初始化对象。void buildDict(String[] dictionary)
使用字符串数组dictionary
设定该数据结构,dictionary
中的字符串互不相同。bool search(String searchWord)
给定一个字符串searchWord
,判定能否只将字符串中一个字母换成另一个字母,使得所形成的新字符串能够与字典中的任一字符串匹配。如果可以,返回True
;否则,返回False
。-
class MagicDictionary(object): def __init__(self): # 初始化一个空的集合,用来存储字典中的单词 self.dictionary = set() def buildDict(self, dictionary): # 将传入的字典添加到我们的集合中 self.dictionary = set(dictionary) def search(self, searchWord): # 遍历searchWord中的每个字符 for i in range(len(searchWord)): # 对于每个字符,尝试将其替换为26个英文字母中的任意一个 for j in range(26): c = chr(ord('a') + j) # 如果替换的字符和原字符不同 if c != searchWord[i]: # 创建一个新的单词,其中第i个字符被替换为c new_word = searchWord[:i] + c + searchWord[i+1:] # 如果这个新单词在我们的字典中,返回True if new_word in self.dictionary: return True # 如果没有任何替换能使得新单词在我们的字典中,返回False return False
学习了字典树的概念,了解到搜索引擎的底层最基本的原理就是「字典树」这种数据结构。字典树通过利用字符串之间的公共前缀来减少查询时间并节约存储空间,非常适合处理大量字符串数据的情景。
-
字典树的应用分为以下几种:
- 字符串检索:事先将已知的⼀些字符串(字典)的有关信息存储到字典树⾥, 查找⼀些字符串是否出现过、出现的频率。
- 前缀统计:统计⼀个串所有前缀单词的个数,只需统计从根节点到叶子节点路径上单词出现的个数,也可以判断⼀个单词是否为另⼀个单词的前缀。
- 最长公共前缀问题:利用字典树求解多个字符串的最长公共前缀问题。将⼤量字符串都存储到⼀棵字典树上时, 可以快速得到某些字符串的公共前缀。对所有字符串都建⽴字典树,两个串的最长公共前缀的长度就是它们所在节点最近公共祖先的长度,于是转变为最近公共祖先问题。
- 字符串排序:利⽤字典树进⾏串排序。例如,给定多个互不相同的仅由⼀个单词构成的英⽂名,将它们按字典序从⼩到⼤输出。采⽤数组⽅式创建字典树,字典树中每个节点的所有⼦节点都是按照其字母⼤⼩排序的。然后对字典树进⾏先序遍历,输出的相应字符串就是按字典序排序的结果。