leetcode
leetcode 701 ~ 750
单词的压缩编码

单词的压缩编码

难度:

标签:

题目描述

代码结果

运行时间: 45 ms, 内存: 16.4 MB


/*
 * 思路:
 * 1. 创建一个Set来存储words数组中所有单词的所有后缀。
 * 2. 使用Stream API遍历每个单词的所有后缀并添加到Set中。
 * 3. 使用Stream API遍历words数组,检查每个单词是否在Set中(即它不是另一个单词的后缀),如果是,则将它的长度加1(因为需要加上'#')。
 * 4. 返回所需长度。
 */

import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;

public class Solution {
    public int minimumLengthEncoding(String[] words) {
        Set<String> suffixes = new HashSet<>();
        Arrays.stream(words).forEach(word -> {
            for (int i = 0; i < word.length(); i++) {
                suffixes.add(word.substring(i));
            }
        });
        return Arrays.stream(words)
                .filter(suffixes::contains)
                .peek(suffixes::remove) // 防止重复计数
                .mapToInt(word -> word.length() + 1)
                .sum();
    }
}

解释

方法:

这个题解的思路是利用字符串排序和后缀匹配来解决问题。首先将所有单词按照反转后的字典序排序,这样可以保证如果一个单词是另一个单词的后缀,那么它一定会出现在那个单词的后面。然后遍历排序后的单词列表,如果当前单词不是下一个单词的后缀,说明当前单词需要被编码,将其长度加1(表示单词结尾的'#')累加到结果中。最后将最后一个单词的长度加1也累加到结果中,因为最后一个单词一定需要被编码。

时间复杂度:

O(n log n)

空间复杂度:

O(n)

代码细节讲解

🦆
为什么在解题中选择将单词进行反转后再排序,这种排序方式对解题有什么帮助?
将单词进行反转再排序的主要目的是将单词的结尾字符放在比较的最前面,这样可以更容易地判断一个单词是否是另一个单词的后缀。在反转后的字典序排序中,如果一个单词是另一个单词的后缀,那么这个单词在排序后的列表中会紧跟在它的超集单词之后。例如,对于单词'apple'和'ple',反转后分别是'elppa'和'elp',在字典序中'elp'会排在'elppa'之前。这样排序后只需比较相邻的单词即可确定编码的必要性,简化了后续的处理过程。
🦆
当判断一个单词是否为另一个单词的后缀时,为什么只检查当前单词与下一个单词而不是所有其他单词?
由于单词已经按照反转后的字典序进行了排序,如果一个单词是另一个单词的后缀,它必然直接位于那个单词之后。这意味着在检查时只需考虑连续的两个单词。如果检查所有其他单词,不仅增加了不必要的计算复杂度,而且因为排序已经确保了必要的相邻关系,这种全面检查是多余的。
🦆
如果有多个单词相同,这种方法处理时会有什么特殊考虑吗?
如果有多个单词完全相同,排序过程中这些单词会紧挨在一起。在编码长度计算时,除了最后一次出现的单词需要计入结果(因为它没有后续单词或是其后续单词不是它的后缀),其他相同的单词不会被重复计算,因为它们都会被判定为后续单词的后缀。这样确保了编码的最小长度,避免了重复的单词导致的冗余。
🦆
解题思路中提到的`如果当前单词不是下一个单词的后缀`,能否给出一个具体的例子说明这种情况?
考虑单词列表['time', 'me', 'bell']。经过反转和排序后,列表变为['em', 'emit', 'lleb']。在这个列表中,'em'(对应原单词'me')不是'emit'(对应原单词'time')的后缀,因此我们需要单独对'me'进行编码,将其长度加上1(为'#'符号)累加到结果中。这个例子说明了即使单词在原始列表中是另一个单词的后缀,在排序和反转后也可能不再满足这种关系,需要单独编码。

相关问题