题目描述
给定一个单词列表,我们将这个列表编码成一个索引字符串 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到str.length()-1个单词组成的后缀是否在所给的单词列表中,如果存在则可以直接将该单词抹除掉,然后计算全部单词的长度+1的和即可。
代码
class Solution { public: int minimumLengthEncoding(vector<string>& words) { set<string> myset(words.begin(), words.end()); for(int i = 0; i < words.size(); i++) for(int j = 1; j < words[i].size(); j++) myset.erase(words[i].substr(j)); int len = 0; for(const string &word : myset) len += word.length() + 1; return len; } };
.
.
.
.
2.字典树(前缀树、Trie树)
当不限定单词长度时,使用后缀法具有较高的时间复杂度,因此可以用字典树来优化时间复杂度。
为了找出不同单词是否具有相同的后缀,需要将单词反向插入到字典树中,例如,我们有 "time" 和 "me",可以将 "emit" 和 "em" 插入字典树中。
步骤
首先需要定义字典树的结构,只考虑小写字符的情况下,共26个字符,因此相当于一个26叉树
class Trie { Trie *child[26]; Trie() { for(int i = 0; i < 26; i++) child[i] = nullptr; } };
接下来,需要在字典树中进行字符的插入,从单词最后一位开始向前遍历,当该字母对应的位置为空,就在该处创建一个节点作为父节点,继续在该节点的基础上处理字符串中的前一个字符。
void insert(Trie* root, string word) { Trie* tmp = root; for(int i = word.length() - 1; i >= 0; i--) { if( tmp->child[word[i]-'a'] == nullptr) //没有该字符的节点就创建节点 tmp->child[word[i]-'a'] = new Trie(); tmp = tmp->child[word[i]-'a']; //这里相当于递归,在该节点后面继续创建节点 } }
创建好字典树后,需要遍历字典树来计算全部节点的个数,采用深度优先搜索进行遍历。设置一个结束标志位,当还有子树时,标志位置为false,没有子树时,即为该条路径遍历结束,保存树的深度。
void dfs(Trie *root, int &ans, int deep) { bool endFlag = true; for(int i = 0; i < 26; i++) { if(root->child[i] != nullptr) { dfs(root->child[i], res, deep + 1); //递归调用,记得深度+1 endFlag = false; } } if(endFlag) res += deep; //叶子节点,结果加上当前深度 }
程序主体,因为字典树的一条路径上字符的数量是节点数-1的,因此如果只是计算字符个数,那么dfs()应该从0开始,而这里因为每个单词后会有一个结束符,占用一个字符,因此是从1开始。
int minimumLengthEncoding(vector<string>& words) { Trie * root = new Trie(); //创建一棵字典树 for(int i = 0; i < words.size(); i++) insert(root, words[i]); //插入 int ans= 0; dfs(root, ans, 1); //从1开始 return ans; }
全部代码
class Trie { public: Trie *childen[26]; Trie() { for(int i = 0; i < 26; i++) childen[i] = nullptr; } }; // // class Solution { public: int minimumLengthEncoding(vector<string>& words) { Trie *root = new Trie(); for(const string &word : words) insert(root, word); int ans = 0; dfs(root, ans, 1); return ans; } void insert(Trie *root, string word) { Trie *temp = root; for(int i = word.length() - 1; i >= 0; i--) { if(temp->childen[word[i] - 'a'] == nullptr) temp->childen[word[i] - 'a'] = new Trie(); temp = temp->childen[word[i] - 'a']; } } void dfs(Trie *root, int &ans, int deep) { bool endFlag = true; for(int i = 0; i < 26; i++) { if(root->childen[i] != nullptr) { dfs(root->childen[i], ans, deep + 1); endFlag = false; } } if(endFlag) ans += deep; } };
```