这题一开始我没完全理解题意,以为最终的结果里原先的元素要保持原本顺序,其实不是。
还有一点理解错了,我以为如果s1的后缀和s2的前缀有重合的话,s1和s2可以组成一个更长的字符串,中间部分是他们共享的,其实理解错了,比如对于 abc 和 bcd,如果按照我的理解,变成了abcd#,然后题目要求的是,对于每一个#,都要找到一个位置i,从i到这个#的字符串是原words数组的一个元素,那么对于abcd#,无论如何找不到一个i使得i到#组成abc。
所以这题要的效果是如果s1是s2的后缀的一部分,那么最终得到的结果字符串里只包含s2,不好含s1。比如abc和bc,应该组成abc#,这样i分别取0和1,再以#为右边界,就能得到abc和bc。还有一点,对于最终的结果,是分成很多组的,每一组都是由有相同后缀的字符串合并出来的,注意被合并的字符串应当整体是另一个更长字符串的后缀。比如abcde和de组成abcde#,而abcde和eg就不行,就只能变成abcde#eg#。
终于搞清楚题意之后就可以做了。
第一种方法是暴力,先把所有字符串放到一个set里,因为要去重,然后对于每个字符串,从下标1开始依次去取子串,然后尝试在set里删除。比如对于abcde,依次在set中尝试删除bcde、cde、de、e。最终剩下的set里每个元素后面加上#,然后随意顺序组合起来就是要的答案。
class Solution {
public int minimumLengthEncoding(String[] words) {
Set<String> set = new HashSet<>(Arrays.asList(words));
for (String w : words) {
for (int i = 1; i < w.length(); i++) {
set.remove(w.substring(i));
}
}
int cnt = 0;
for (String s : set) {
cnt += s.length() + 1;
}
return cnt;
}
}
另一种做法使用Trie树,字典树,正好复习一下这个数据结构,属于是空间换时间,利用字符串的公共前缀来减少查询时间,最大限度地减少无谓的字符串比较,查询效率比哈希树高。
注意到,如果两个字符串有公共前缀的话,那么应该保存在Trie同一条路径里。而这题要的是整体是另个字符串后缀的字符串,那么可以把原始字符串全部反过来,然后存在Trie里,再去遍历每个字符串,遍历到最后一个字母时,判断是不是叶子节点,如果是的话,说明这个字符串应该添加到结果里。举例子,time和me,反过来之后时emit和em,显然它们存在一个路径下面,遍历em到m时发现m还有叶子节点,说明em反过来的话必定是其他字符串的后缀,所以me不能加到结果里。
至于怎么判断一个节点是不是叶子节点,因为每一个TrieNode都有一个TrieNode[] next结构,并且这个数组在创建TrieNode时就创建了,但是数组里每个元素默认都是null,所以就遍历这个next数组,只要有一位不是null说明就不是叶子节点。
class Solution {
public int minimumLengthEncoding(String[] words) {
Set<String> reverseWords = new HashSet<>();
for (String w : words) {
reverseWords.add(new StringBuilder(w).reverse().toString());
}
Trie trie = new Trie();
for (String w : reverseWords) {
trie.insert(w);
}
int cnt = 0;
for (String w : reverseWords) {
if (trie.isLeaf(trie.travel(w))) {
cnt += w.length() + 1;
}
}
return cnt;
}
}
class Trie {
TrieNode root;
public Trie() {
root = new TrieNode(-1);
}
public void insert(String s) {
TrieNode cur = root;
for (int i = 0; i < s.length(); i++) {
int c = s.charAt(i) - 'a';
if (cur.next[c] == null) {
cur.next[c] = new TrieNode(c);
}
cur = cur.next[c];
}
cur.isEnd = true;
}
public TrieNode travel(String s) {
TrieNode cur = root;
for (int i = 0; i < s.length(); i++) {
int c = s.charAt(i) - 'a';
if (cur.next[c] == null) {
return null;
}
cur = cur.next[c];
}
return cur;
}
public boolean isLeaf(TrieNode t) {
for (int i = 0; i < t.next.length; i++) {
if (t.next[i] != null) {
return false;
}
}
return true;
}
public boolean search(String s) {
TrieNode cur = root;
for (int i = 0; i < s.length(); i++) {
int c = s.charAt(i) - 'a';
if (cur.next[c] == null) {
return false;
}
cur = cur.next[c];
}
return cur.isEnd;
}
}
class TrieNode {
int val;
public TrieNode(int v) {
val = v;
}
boolean isEnd;
TrieNode[] next = new TrieNode[26];
}