题目信息:820单词的压缩编码单词的压缩编码
难度: 中等
分析和最初的答案
检查一个单词是不是另一个单词的后缀, 如果是, 这个单词不编码.
那我最先想到的就是遍历单词表, 删除这个单词的每个后缀, 最终保留所有不是其他单词后缀的单词
def foo(words):
words_ = set(words)
for w in words:
for i in range(1, len(w)):
words_.discard(w[i:])
return sum(len(w)+1 for w in words_)
上面是看了官方题解后修改的, 原先不熟悉集合的用法:
def foo(words):
words = list(set(words))
for w in words.copy():
for i in range(1, len(w)):
words = [j for j in words if j != w[i:]] # 这里如果用生成器会有问题, i和w的引用会改变
return sum(len(w)+1 for w in words)
复杂度分析:时间复杂度, 对每个单词所有后缀要进行检查
(没有开启对数学公式的支持, 先语言描述吧)空间复杂度: 所有单词的长度(x2)
字典树
基本性质:根节点不包含字符,除根节点外每一个节点都只包含一个字符
从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串
这是维护字符串的前缀的特点每个节点的所有子节点包含的字符都不相同。
分析与官方答案
这里只需倒着构建字典树,最终每一条路径(从二级节点到叶节点)就是不是其他单词后缀的单词,但是统计所有完整路径的效率并不高(字典树擅长的是检查,从一端开始检查而不必遍历所有单词,适合找出一条路径),可以检查每一个单词。LeetCode官方的字典树解法保存了每个单词第一个字母所在的节点,用以判断这个这个单词是否能占有一条完整的路径,即判断是否是不是其他单词后缀的单词,同时遍历单词表和节点表,用单词表里的单词来统计字符串长度。
from collections import defaultdict
from functools import reduce
def foo(words):
words = list(set(words))
# Tire是一个返回defaultdict的函数,defaultdict的默认值又是Tire
Trie = lambda: defaultdict(Trie)
trie = Trie()
# 所有单词对应的第一个字母所在的节点
nodes = [reduce(dict.__getitem__, word[::-1], trie)
for word in words]
# 同时遍历单词表和节点表
return sum(len(word) + 1
for word, node in zip(words, nodes)
if len(node) == 0)
实现一个字典树
class Trie:
def __init__(self):
self._trie = {}
def insert(self, word):
node = self._trie
for c in word:
if c not in node:
node[c]={}
node = node[c]
node['#'] = True
def delete(self, word):
node = self._trie
for c in word:
if c in node:
node = node[c]
else:
return
node.pop('#', None)
def __contains__(self, word):
node = self._trie
for c in word:
if c in node:
node = node[c]
else:
return False
if '#' in node:
return True
else:
return False
排序后检查
单词按照字母顺序排序后,前缀相同的单词相邻
因而可以反转后排序,再遍历,检查一个单词和它下一个单词(如果是默认的排序)
def foo3(words):
words = sorted(set(words), key=lambda w:w[::-1])
N = len(words)
words = [w for i,w in enumerate(words) if not (i+1
return sum(len(w)+1 for w in words)
字典树是真的没有学过,但是这个方法也没有想到,就想着暴力遍历了。。。