LeetCode #820 单词的压缩编码 字符串 字典树

LeetCode #820 单词的压缩编码

题目描述

给定一个单词列表,我们将这个列表编码成一个索引字符串 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. 1 <= words.length <= 2000
  2. 1 <= words[i].length <= 7
  3. 每个单词都是小写字母 。

方法一:存储后缀

一开始被题目误导,以为像 me 这种后缀只会出现在 time 后面,其实顺序是可以变的,测试跑过了提交没过 >︿<,还有 indexes 其实只是帮助你理解题目意思而已,不用存储!

# 错误解法
class Solution:
    def minimumLengthEncoding(self, words: List[str]) -> int:
        ansStr = ""
        ansList = []
        for i in words:
        	# 每次都判断当前单词是否为字符串的后缀
            if ansStr.endswith(i + '#'):
                ansList.append(len(ansStr) - len(i) - 1)
            else:
                ansStr += i + '#'
                ansList.append(len(ansStr))

        return len(ansStr)

因为一个单词最长为 7 位,最多有 6 个后缀,可以用集合存储所有单词,枚举后缀拿去和集合对比,存在则从集合中删去

# 正确解法
class Solution:
    def minimumLengthEncoding(self, words: List[str]) -> int:
        good = set(words)

        for word in words:
            for k in range(1, len(word)):
                good.discard(word[k:])
        return sum(len(word) + 1 for word in good)
  • 时间复杂度: O ( Σ w i 2 ) O(Σw_i^2) ,其中 O ( Σ w i 2 ) O(Σw_i^2) words[i] 的长度
  • 空间复杂度: O ( Σ w i ) O(Σw_i) ,存储单词的空间开销

方法二:反转 + 排序

评论区 @nettee,由于要判断每个单词是否为其他单词的后缀,在最坏情况下要遍历 n 2 n^2 次,但是如果将所有单词反转过来再排序,就只需要比较当前单词是否为下一个单词的前缀:
在这里插入图片描述

class Solution:
    def minimumLengthEncoding(self, words: List[str]) -> int:
        N = len(words)
        reversed_words = []

        # 反转
        for word in words:
            reversed_words.append(word[::-1])

        # 排序
        reversed_words.sort()
        
        ans = 0
        for i in range(1, N):
            if (reversed_words[i].startswith(reversed_words[i-1])):
                pass
            else:
                ans += len(reversed_words[i-1]) + 1 # 要加上一个 '#'

        # 排序后的最后一个单词是没有比较的,但是它肯定不会是其他单词的前缀,直接加上
        return ans + len(reversed_words[N-1]) +1

优化空间,直接用原列表存储顺序(单词不做反转,所以是判断后缀)
在这里插入图片描述

class Solution:
    def minimumLengthEncoding(self, words: List[str]) -> int:
        N = len(words)
        words.sort(key=lambda word: word[::-1])
        
        ans = 0
        for i in range(1, N):
            if (words[i].endswith(words[i-1])):
                pass
            else:
                ans += len(words[i-1]) + 1 # 要加上一个 '#'

        # 排序后的最后一个单词是没有比较的,但是它肯定不会是其他单词的前缀,直接加上
        return ans + len(words[N-1]) +1

方法三:按长度排序 + 搜索

评论区 @Jejune,这个方法真是太巧妙了!

class Solution:
    def minimumLengthEncoding(self, words: List[str]) -> int:
    	# 这里可以先用set来去重
        words = sorted(words, key=lambda i:len(i), reverse=True)
        S = ''
        # 长的在前短的在后,用 # 绑定了词尾,所以有一个单词是另一个单词的后缀的话一定会命中
        for word in words:
            if word+'#' not in S:
                S+= word+'#'
        return len(S)

方法四:字典树

python没学好看不太懂。。。leetcode的官方解法

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)
发布了67 篇原创文章 · 获赞 2 · 访问量 1361

猜你喜欢

转载自blog.csdn.net/weixin_42511320/article/details/105155459