【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:字符串翻转+排序(时间复杂度较高,占用内存少)
class Solution {
public:
int minimumLengthEncoding(vector<string>& words) {
for (string& w : words) reverse(w.begin(), w.end()); // 字母逆序
sort(words.begin(), words.end()); // 按字典序排序
int res = 0;
for (int i = 0; i < words.size() - 1; i++)
if (check(words[i], words[i + 1])) res += words[i].size() + 1;
return res + words.back().size() + 1;
}
private:
bool check(string& a, string& b) { // a为b前缀时返回0
if (a.size() > b.size()) return 1;
for (int i = 0; i < a.size(); i++)
if (a[i] != b[i]) return 1;
return 0;
}
};
重点思路
首先翻转words中字符串,再使用sort排序。sort对字符串的默认排序方法是先比较字母大小,再比较长短,字母小的短的排在前面。翻转排序后,只需要比较相邻两个字符串即可。当前者不为后者前缀时,加上该字符的长度。
解答3:前缀树、字典树、Trie方法(占用内存多,时间效率高)
class Trie {
public:
int isLeaf = 1; // 是否是叶节点
int len = 0; // 单词长度
Trie* insert(string& s) {
Trie* root = this;
for (int i = s.size() - 1; i >= 0; i--) {
int index = s[i] - 'a';
if (!root->next[index]) {
root->next[index] = new Trie();
root->isLeaf = 0;
}
root = root->next[index];
}
root->len = s.size();
return root; // 返回当前指针,后续会使用对应的len和isLeaf
}
private:
Trie* next[26]{};
};
class Solution {
public:
int minimumLengthEncoding(vector<string>& words) {
int res = 0;
unordered_set<Trie*> ust; // 用于去重
Trie* t = new Trie();
for (string& w : words)
ust.insert(t->insert(w));
for (auto& u : ust) // 结果包括所有位于叶子节点的单词长度
if (u->isLeaf) res += u->len + 1;
return res;
}
};
重点思路
由于该题需要大量运用字符串的前缀or后缀,很容易想到Trie方法。由于Trie为前缀树,所以放入Trie时使用倒序。本题使用所有叶子结点处所代表的字符串总和。本题使用unordered_set
对输入进行去重,每个返回的节点含is_leaf
成员变量来表示是否为叶子结点,len
成员变量表示该单词的长度。最后只需要将ust
中isLeaf = 1
的成员长度想加即可。