主管期望你来实现英文输入法单词联想功能,需求如下:
注意:
输入两行。
首行输入一段由英文单词word
和标点
构成的语句str
,接下来一行为一个英文单词前缀pre
。
0 < word.length() <= 20`,`0 < str.length() <= 10000`,`0 < pre.length() <= 20
输出符合要求的单词序列或单词前缀。存在多个时,单词之间以单个空格分割
I love you
He
He
用户已输入单词语句"I love you"
,可以提炼出"I"
,"love"
,"you"
三个单词。接下来用户输入"He"
, 从已经输入信息中无法联想到符合要求的单词,所以输出用户输入的单词前缀。
The furthest distance in the world,Is not between life and death,But when I stand in front or you,Yet you don't know that I love you.
front furthest
首先我们需要处理输入,将输入的字符串s
根据标点符号和空格隔开,得到一个由若干单词word
组成的单词列表lst
。这里稍微有点麻烦,不能再用我们熟悉的split()
方法完成,而是改为较为麻烦的遍历写法。
首先我们初始化lst = [""]
,即单词列表中存放了一个空字符串。然后我们遍历字符串s
中的字符ch
,当
ch
是字母,则将其加入到lst
最后一个元素的末尾,即延长当前单词。如果此时lst[-1]
为一个空字符串""
,则ch
充当了某个单词首位字母的角色。ch
不是字母,说明遇到一个标点符号,当前单词的获取已经结束,lst
的末尾插入一个新的空字符串""
。上述思路整理为代码后即为:
lst = [""]
for ch in s:
if ch.isalpha():
lst[-1] += ch
else:
lst.append("")
当然这个过程也可用正则表达式以更加简短的代码来完成,但这部分知识已经严重超纲,大部分题目完全用不上,学有余力的同学可以自行研究一下。
得到lst
之后,剩下的工作就相当简单了。由于lst
中可能出现重复单词,我们使用哈希集合进行去重操作。又因为最后的输出要求按照字典序排序,因此去重之后再对哈希集合进行调用sorted()
内置函数,再转化为列表。
lst_sorted = list(sorted(set(lst)))
对于lst_sorted
中的每一个单词word
,我们可以使用切片来获得其前pre_length
个字符所构成的字符串,并与pre
进行比较,就能够得知word
是否包含前缀pre
了。
pre_length = len(pre)
for word in lst_sorted:
if word[:pre_length] == pre:
ans.append(word)
总体来说本题难度不大,甚至很难归类为哪一种具体的算法(用到去重功能就姑且认为是哈希集合吧)。难点其实主要在于对输入的处理,初始化lst = [""]
实际上是一个颇有技巧的做法。
当然本题还存在着前缀树的最优解法,但也严重超纲,不要求掌握。
# 题目:2023Q1A-英文输入法
# 分值:100
# 作者:许老师-闭着眼睛学数理化
# 算法:哈希集合
# 代码看不懂的地方,请直接在群上提问
s = input()
pre = input()
# 初始化列表lst用于存放所有单词
lst = [""]
# 遍历s中的所有字符ch,如果
# 1. ch是字母,则加入到lst最后一个元素的末尾,即延长当前单词
# 2. ch不是字母,说明遇到一个标点符号,结束当前单词的获取,lst的末尾插入一个新的空字符串""
# 这个过程也可以使用正则表达式来完成,不要求掌握,学有余力的同学可以自学一下
for ch in s:
if ch.isalpha():
lst[-1] += ch
else:
lst.append("")
# 用哈希集合去重lst中可能出现的重复单词
# 去重后进行排序,排序后在转化为列表lst_sorted
lst_sorted = list(sorted(set(lst)))
# 初始化答案数组
ans = list()
# 获得pre的长度,用于切片
pre_length = len(pre)
# 遍历lst_sorted中的每一个单词
for word in lst_sorted:
# 如果word前pre_length个字符的切片等于pre
# 说明word的前缀是pre,将其加入答案数组ans中
if word[:pre_length] == pre:
ans.append(word)
# 如果ans长度大于0,说明至少存在一个单词的前缀是pre,输出由所有单词组成的字符串
# 如果ans长度等于0,说明不存在任何一个单词的前缀是pre,返回pre
print(" ".join(ans) if len(ans) > 0 else pre)
时间复杂度:O(NlogN + NK)
。排序需要的时间复杂度为O(NlogN)。遍历lst_sorted
需要O(N)
的复杂度,每次对word
进行切片操作需要O(K)
的复杂度,故遍历过程共需要O(NK)
的时间复杂度。总的时间复杂度为两者相加,即O(NlogN + NK)
,如果N
远大于K
,也会退化成O(NlogN)
。
空间复杂度:O(NM)
。主要为lst_sorted
的所占空间。
N
为单词数目,M
为单词平均长度,K
为前缀单词pre
的长度。
(前缀树解法,不要求掌握,感兴趣的同学可以研究一下)
# 题目:2023Q1A-英文输入法
# 分值:100
# 作者:许老师-闭着眼睛学数理化
# 算法:前缀树
# 代码看不懂的地方,请直接在群上提问
# 构建前缀树节点类
class Trie():
def __init__(self) -> None:
self.children = [None] * 52 # 大小写均存在,需要构建长度为52的children列表
self.isEnd = False # 结束标识符,True表示当前节点是一个单词的结尾
# 将单词word加入前缀树的函数
def addword(self, word):
node = self
# 遍历该单词中的所有字符
for ch in word:
# 获得ch在children列表中对应的索引
ch_idx = self.getIdx(ch)
# 如果对应位置为None
if node.children[ch_idx] is None:
# 则为这个ch字符创建一个新的前缀树节点
node.children[ch_idx] = Trie()
# 令前缀树节点前进到ch所在的节点
node = node.children[ch_idx]
# 完成该单词的添加,设置最后一个字符的节点的结束标识符为True
node.isEnd = True
# 根据字符ch获得在children列表中的对应索引的函数
def getIdx(self, ch):
# 如果ch是小写,得到26-51的索引
if ch.islower():
ch_idx = ord(ch) - ord("a") + 26
# 如果ch是大写,得到0-25的索引
else:
ch_idx = ord(ch) - ord("A")
return ch_idx
# 根据在children列表中的索引idx获得对应字符ch的函数
def getCh(self, idx):
# 如果idx大于等于26,是一个小写字母
if idx >= 26:
ch = chr(idx + ord("a") - 26)
# 如果idx小于26,是一个大写字母
else:
ch = chr(idx + ord("A"))
return ch
# 获得前缀prefix最后一个字符所在的节点
def getLastNode(self, prefix):
node = self
for ch in prefix:
ch_idx = self.getIdx(ch)
if node.children[ch_idx] is None:
return None
node = node.children[ch_idx]
return node
# 对前缀树进行dfs前序遍历,搜索得到所有后缀
def dfs(self, pre, ans, path):
node = self
# 遇到一个单词结束标识符,将当前path合并为字符串后加入ans
if node.isEnd:
# 要注意path此时仅仅是后缀,要得到完整的单词字符串还要在前面加上pre
ans.append(pre + "".join(path))
# 如果node.children存在任意一个非None节点,需要对非空节点继续进行DFS搜索
if any(node.children):
# 遍历node.children中的所有下一个节点nxt_node
for nxt_idx, nxt_node in enumerate(node.children):
# 如果nxt_node不为空,则继续递归地进行DFS搜索
if nxt_node is not None:
# 根据nxt_idx获得对应的字符nxt_ch
nxt_ch = self.getCh(nxt_idx)
# 将字符nxt_ch加在path末尾的结果,作为参数传入nxt_node的dfs递归
nxt_node.dfs(pre, ans, path + [nxt_ch])
s = input()
pre = input()
# 初始化列表lst用于存放所有单词
lst = [""]
# 遍历s中的所有字符ch,如果
# 1. ch是字母,则加入到lst最后一个元素的末尾,即延长当前单词
# 2. ch不是字母,说明遇到一个标点符号,结束当前单词的获取,lst的末尾插入一个新的空字符串""
# 这个过程也可以使用正则表达式来完成,不要求掌握,学有余力的同学可以自学一下
for ch in s:
if ch.isalpha():
lst[-1] += ch
else:
lst.append("")
# 对lst进行去重,因为使用前缀树,所以无需排序
lst = list(set(lst))
# 初始化前缀树根节点
root = Trie()
# 遍历lst中的每一个单词word,构建前缀树
for word in lst:
root.addword(word)
# 调用前缀树中的getLastNode()方法,得到前缀pre在树中的最后一个节点
lastNode = root.getLastNode(pre)
# 如果lastNode为空,说明在root前缀树中,不存在任何前缀为pre的单词,输出pre
if lastNode is None:
print(pre)
# 如果lastNode非空,说明在root前缀树中,存在前缀为pre的单词,要找到所有单词
else:
# 初始化答案数组
ans = list()
# 从lastNode开始,调用dfs,找到所有单词,按顺序储存在ans中
lastNode.dfs(pre, ans, [])
# 最后将ans用空格隔开合并为字符串后输出
print(" ".join(ans))
时间复杂度:O(NM)
。建树、检查前缀的时间复杂度。
空间复杂度:O(D)
。
N
为单词数目,M
为单词平均长度,D
为前缀树的节点数,远小于NM
。
华为OD算法冲刺训练
华为OD算法冲刺训练目前开始常态化报名!目前已服务100+同学成功上岸!
课程讲师为全网50w+粉丝编程博主@吴师兄学算法 以及小红书头部编程博主@闭着眼睛学数理化
每期人数维持在20人内,保证能够最大限度地满足到每一个同学的需求,达到和1v1同样的学习效果!
30+天陪伴式学习,20+直播课时,300+动画图解视频,200+LeetCode经典题,100+华为OD真题,还有简历修改与模拟面试将为你解锁
可查看链接 OD算法冲刺训练课程表 & OD真题汇总(持续更新)
绿色聊天软件戳 sheepvipvip了解更多