给定一个单词列表,我们将这个列表编码成一个索引字符串 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.每个单词都是小写字母 。
方法一:存储后缀
思路:
如果单词X是Y的后缀,那么单词X就不需要考虑了,因为编码Y的时候就同时将X编码了。例如,如果words中同时用me和time,则可以不考虑me。
如果单词Y不在任何别的单词X的后缀中出现,那么Y一定是编码字符串的一部分。
因此,目标就是保留所有不是其他单词后缀的单词,最后的结果就是这些单词长度加一的总和,因为每个单词编码后后面还需要跟一个#符号。
算法:
由数据范围可知一个单词最多含有7个后缀,所以我们可以枚举单词所有的后缀。对于每个后缀,如果其存在words列表中,我们就将其从列表中删除。为了高效删除,我们将words用哈希集合(HashSet)来存储。
class Solution {
public:
int minimumLengthEncoding(vector<string>& words) {
unordered_set<string> good(words.begin(), words.end());
for (const string& word : words){
for (int k = 1; k < word.size(); k++){
good.erase(word.substr(k));
}
}
int ans = 0;
for (const string& word: good){
ans += word.size() + 1;
}
return ans;
}
};
时间复杂度:
,其中
是words[i]的长度。每个单词有
个后缀,对于每个后缀,查询其是否在集合中时需要进行
的哈希值计算。
空间复杂度:
,存储单词的空间开销。
方法二:字典树
字典树:又称为单词查找树,Trie树,是一种树形结构,是一种哈希树的变种。典型应用是用于统计,排序和保存大量的字符串(不仅限于字符串),所以经常被搜索引擎系统用于文本词频统计。它的优点是:利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。
性质:
它有三个基本性质:
- 根节点不包含字符,除根节点以外每一个节点都只包含一个字符;
- 从根节点到某一节点,路径上经过的字符连接起来,为该节点对应的字符串;
- 每个节点的所有子节点包含的字符都不相同;
在字典树中搜索的方法:
(1)从根结点开始一次搜索;
(2)取得要查找关键词的第一个字母,并根据该字母选择对应的子树继续进行检索;
(3)在相应的子树上,取得要查找关键词的第二个字母,并进一步选择对应的子树进行检索;
(4)迭代过程;
(5)在某个结点处,关键词的所有字母已被取出,则读取附在该结点上的信息,即完成查找。
这道题中,为了找到相同的后缀,我们可以将单词反序之后插入字典树中。最后,字典树的叶子节点就代表不是别的单词后缀的单词,统计叶子节点代表的单词长度加一的和即为我们要的答案。
在这里插入代码片
时间复杂度:
,其中
是words[i]的长度。对于每个单词中的每个字母,只需要进行常数次操作。
空间复杂度:
,字典树的空间开销,其中S为字符集大小。