【leetcode系列】【算法】【中等】单词的压缩编码

题目:

题目链接: https://leetcode-cn.com/problems/short-encoding-of-words/

 

解题思路:

1. 基础解法,算法实现逻辑如下:

  1. 对单词列表进行去重
  2. 对去重后的列表进行排序,排序规则为:根据单词长度逆序(防止长字符串在后,短字符串在前,结果在最终字符串中保存了两份数据,比如['time', 'me']和['me', 'time']的区别)
  3. 对排序后的元素进行遍历,并添加变量s保存字符串,s的更新逻辑如下:
    1. 如果当前单词curr_s + '#'能够在s中匹配到,则不更新s
    2. 如果curr_s + '#'无法在s中匹配上,则更新s += curr_s + '#'
  4. 返回s的长度

 

2. 字典树

问题可以转换为,匹配当前档次,是否为其他单词的后缀

如果是,当前单次不需要记录长度

如果不是,就需要记录单词长度 + 1

 

根据这一点,可以对单词逆序排序后,对字母建立字典树,然后对叶子节点的高度 + 1进行求和,就是最短的字符串长度

假设单词列表为:['time', 'me', 'abe', 'el', 'bell'],对每个单词逆序后的列表为['emit', 'em', 'eba', 'le', 'lleb'], 最终建立的树如下:

fig2

 

代码实现:

方法一:

class Solution:
    def minimumLengthEncoding(self, words: List[str]) -> int:
        new_words = list(set(words))
        new_words.sort(key = len, reverse = True)
        s = ''
        for curr_s in new_words:
            if -1 == s.find(curr_s + '#'):
                s += curr_s + '#'
                
        return len(s)

 

方法二(借用官方题解中Lucien的评论):

一步步从最正常的写法走向Pythonic的解。

# 定义字典树中的一个节点
class Node(object):
    def __init__(self):
        self.children={}
class Solution:
    def minimumLengthEncoding(self, words: List[str]) -> int:
        words = list(set(words)) #需要去重,否则在之后计算“叶子高度”的时候会重复计算
        trie=Node() #这是字典树的根
        nodes=[] #这里保存着每个word对应的最后一个节点,比如对于单词time,它保存字母t对应的节点(因为是从后往前找的)
        for word in words:
            now=trie
            for w in reversed(word):
                if w in now.children:
                    now=now.children[w]
                else:
                    now.children[w]=Node()
                    now=now.children[w]
            nodes.append(now)
        ans=0
        for w,c in zip(words,nodes):
            if len(c.children)==0: #没有children,意味着这个节点是个叶子,nodes保存着每个word对应的最后一个节点,当它是一个叶子时,我们就该累加这个word的长度+1,这就是为什么我们在最开始要去重
                ans+=len(w)+1
        return ans

相信以上的解答大家可以看懂,那么就从Node开始简化。原先我们把Node声明为一个类,但这个类中只有一个字典,所以我们不如就直接用一个字典来表示节点,一个空字典以为着这是一个叶子节点,否则字典中的每一个元素都是它的一个孩子,上面的代码可以简化为:

class Solution:
    def minimumLengthEncoding(self, words: List[str]) -> int:
        words = list(set(words)) #需要去重,否则在之后计算“叶子高度”的时候会重复计算
        trie={} #这是字典树的根
        nodes=[] #这里保存着每个word对应的最后一个节点,比如对于单词time,它保存字母t对应的节点(因为是从后往前找的)
        for word in words:
            now=trie
            for w in reversed(word):
                if w in now:
                    now=now[w]
                else:
                    now[w]={}
                    now=now[w]
            nodes.append(now)
        ans=0
        for w,c in zip(words,nodes):
            if len(c)==0: #一个空字典,意味着这个节点是个叶子
                ans+=len(w)+1
        return ans

继续简化,我们不想在生成字典树时每次都判断“当前字典有没有这个键”,我们希望,有这个键,就返回它的值,否则返回一个空字典给我。很自然,我们需要用到defaultdict,它默认返回一个字典。但,只是返回一个普通字典吗?比如defaultdict(dict)? 不行,实际上它需要返回一个defaultdict,且这个defaultdict仍旧会递归地返回defaultdict。于是,递归地,我们定义这样一个函数,它返回一个defaultdict类型,且它的默认值是该类型本身。 Trie = lambda: collections.defaultdict(Trie) ,注意,这里的Trie是一个函数,它返回一个defaultdict实例。有了它,我们创建字典树的过程就变成了:

nodes=[]
Trie = lambda: collections.defaultdict(Trie)
trie = Trie()
for word in words:
    now=trie
    for w in word[::-1]:
        now=now[w]
    nodes.append(now)

更进一步,可以简化为
 

nodes=[]
Trie = lambda: collections.defaultdict(Trie)
trie = Trie()
for word in words:
    nodes.append(trie[word[-1]][word[-2]].........)

它就变成了

nodes = [reduce(dict.__getitem__, word[::-1], trie)
                 for word in words]

先不管数组的推导式,单看数组的一项 reduce(dict.__getitem__, word[::-1], trie),reduce三个参数分别为:方法,可循环项,初始值。即它初始值是trie,按照word[::-1]的循环顺序,每次去执行方法dict.__getitem__,且将这个输出作为下次循环的输入,所以它就是trie[word[-1]][word[-2]].........的意思。

最终优化后的代码整体为:

class Solution:
    def minimumLengthEncoding(self, words: List[str]) -> int:
        words = list(set(words)) #remove duplicates
        #Trie is a nested dictionary with nodes created
        # when fetched entries are missing
        Trie = lambda: collections.defaultdict(Trie)
        trie = Trie()

        #reduce(..., S, trie) is trie[S[0]][S[1]][S[2]][...][S[S.length - 1]]
        nodes = [reduce(dict.__getitem__, word[::-1], trie)
                 for word in words]

        #Add word to the answer if it's node has no neighbors
        return sum(len(word) + 1
                   for i, word in enumerate(words)
                   if len(nodes[i]) == 0)

 

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值