题目
给定一个单词列表,我们将这个列表编码成一个索引字符串 S 与一个索引列表 A
。
例如,如果这个列表是 ["time", "me", "bell"]
,我们就可以将其表示为 S = "time#bell#"
和 indexes = [0, 2, 5]
。
对于每一个索引,我们可以通过从字符串 S
中索引的位置开始读取字符串,直到 “#” 结束,来恢复我们之前的单词列表。
那么成功对给定单词列表进行编码的最小字符串长度是多少呢?
示例:
输入: words = ["time", "me", "bell"]
输出: 10
说明: S = "time#bell#" , indexes = [0, 2, 5] 。
提示:
1 <= words.length <= 2000
1 <= words[i].length <= 7
- 每个单词都是小写字母 。
解题思路
解法一:反转+排序
假设有一对单词a和b,如果a是b的后缀,比如me是time的后缀,那么就删除单词a。当遍历完所有单词后,剩下的单词就是构成索引字符串的单词。关键之处是怎么找出这些单词来,如果正向遍历单词则太过麻烦,所以本题思路就是逆向遍历单词,所以先将所有的单词进行反转,并按字典序进行排序。如果a是b的后缀,那么反转后a’就是b’的前缀,按字典序排序后,b’就紧跟在a’后面。对按字典序排序好的反转之后的单词,如果当前单词是下一个单词的前缀,则删除(跳过)它;否则计算它的长度,并加上’#'的1字符长度。
解法二:字典树
前缀树的 api 主要有以下几个:
insert(word): 插入一个单词。
search(word):查找一个单词是否存在。
startWith(word): 查找是否存在以 word 为前缀的单词。
isTail(word):查找word是否为其他单词的前缀,是则返回False,否则返回True。
这道题需要考虑特例, 比如这个列表是 [“time”, “time”, “me”, “bell”] 这种包含重复元素的情况,这里使用set集合来去重。
复杂度分析:
时间复杂度:O(N),其中N为单词长度列表中的总字符数,比如[“time”, “me”],就是 4+2=6。
空间复杂度:O(N),其中N为单词长度列表中的总字符数,比如[“time”, “me”],就是 4+2=6。
代码
解法一:反转+排序
class Solution:
def minimumLengthEncoding(self, words: List[str]) -> int:
N = len(words)
# 反转之后的单词列表
reversed_word = []
for word in words:
# 将单词进行反转
reversed_word.append(word[::-1])
# 字典序排序
reversed_word.sort()
res = 0
for i in range(N):
# 如果当前单词是下一个单词的前缀,则跳过它
if i+1<N and reversed_word[i+1].startswith(reversed_word[i]):
pass
else:
res += len(reversed_word[i]) + 1
return res
解法二:字典树
class Trie:
def __init__(self):
# 初始化字典树结构
self.Trie = {}
def insert(self, word):
# 向字典树中插入单词word
curr = self.Trie
for w in word:
if w not in curr:
curr[w] = {}
curr = curr[w]
# 标记单词结尾
curr['#'] = 1
def isTail(self, word):
# 判断当前单词word是否是从根节点到叶节点组成,即是否为其他单词的前缀
curr = self.Trie
for w in word:
curr = curr[w]
return True if len(curr)==1 else False
class Solution:
def minimumLengthEncoding(self, words: List[str]) -> int:
trie = Trie()
words = set(words) # 单词去重
res = 0
for word in words:
trie.insert(word[::-1]) # 逆序插入
for word in words:
if trie.isTail(word[::-1]):
res += len(word) + 1
return res