LeetCode 820 - Short Encoding of Words


https://leetcode.com/problems/short-encoding-of-words/description/
Given a list of words, we may encode it by writing a reference string S and a list of indexes A.
For example, if the list of words is ["time", "me", "bell"], we can write it as S = "time#bell#" and indexes = [0, 2, 5].
Then for each index, we will recover the word by reading from the reference string from that index until we reach a "#" character.
What is the length of the shortest reference string S possible that encodes the given words?
Example:
Input: words = ["time", "me", "bell"]
Output: 10
Explanation: S = "time#bell#" and indexes = [0, 2, 5].
Note:
  1. 1 <= words.length <= 2000.
  2. 1 <= words[i].length <= 7.
  3. Each word has only lowercase letters.
https://leetcode.com/problems/short-encoding-of-words/discuss/125784/Trie-Solution
2. Insert all reversed words to the trie.
For example, for word "time", we insert 'e', 'm', 'i', 't' successively.


3. Return the sum of depth of all leaves.
One way is to write a simple recursive function to do it.
Here I save all last nodes of words and its depth to a list and all leaves should be in this list.
I iterate this list and all nodes without children are leaved.
    public int minimumLengthEncoding(String[] words) {
        TrieNode root = new TrieNode();
        List<TrieNode> leaves = new  ArrayList<TrieNode>();
        for (String w : new HashSet<>(Arrays.asList(words))) {
            TrieNode cur = root;
            for (int i = w.length() - 1; i >= 0; --i) {
                char j = w.charAt(i);
                if (!cur.next.containsKey(j)) cur.next.put(j, new TrieNode());
                cur = cur.next.get(j);
            }
            cur.depth = w.length() + 1;
            leaves.add(cur);
        }
        int res = 0;
        for (TrieNode leaf : leaves) if (leaf.next.isEmpty()) res += leaf.depth;
        return res;
    }


As in Approach #1, the goal is to remove words that are suffixes of another word in the list.
Algorithm
To find whether different words have the same suffix, let's put them backwards into a trie (prefix tree). For example, if we have "time" and "me", we will put "emit" and "em" into our trie.
After, the leaves of this trie (nodes with no children) represent words that have no suffix, and we will count sum(word.length + 1 for word in words).
  • Time Complexity: O(\sum w_i), where w_i is the length of words[i].
  • Space Complexity: O(\sum w_i), the space used by the trie.
  public int minimumLengthEncoding(String[] words) {
    TrieNode trie = new TrieNode();
    Map<TrieNode, Integer> nodes = new HashMap<>();

    for (int i = 0; i < words.length; ++i) {
      String word = words[i];
      TrieNode cur = trie;
      for (int j = word.length() - 1; j >= 0; --j)
        cur = cur.get(word.charAt(j));
      nodes.put(cur, i);
    }

    int ans = 0;
    for (TrieNode node : nodes.keySet()) {
      if (node.count == 0)
        ans += words[nodes.get(node)].length() + 1;
    }
    return ans;
  }
}

class TrieNode {
  TrieNode[] children;
  int count;

  TrieNode() {
    children = new TrieNode[26];
    count = 0;
  }

  public TrieNode get(char c) {
    if (children[c - 'a'] == null) {
      children[c - 'a'] = new TrieNode();
      count++;
    }
    return children[c - 'a'];

  }


public int minimumLengthEncoding(String[] words) {
  Trie trie = new Trie();
  for (String word : words) {
    trie.insert(new StringBuilder(word).reverse().toString());
  }

  return trie.getLongestWords().stream().map(str -> str.length() + 1).mapToInt(i -> (int) i).sum();
}

private static class TrieNode {
  public TrieNode[] children = new TrieNode[26];
  public boolean isEnd;
  public boolean hasChild;

  @Override
  public String toString() {
    return "TrieNode [children=" + Arrays.toString(children) + ", isEnd=" + isEnd + "]";
  }
}

private static class Trie {
  public TrieNode root = new TrieNode();
  // em, emtia, will only contains emtia
  private Set<String> longestWords = new HashSet<>();

  public void insert(String word) {
    TrieNode curr = root;
    StringBuilder currWord = new StringBuilder();
    for (char ch : word.toCharArray()) {
      int index = ch - 'a';
      TrieNode child = curr.children[index];
      curr.hasChild = true;
      if (child == null) {
        child = new TrieNode();
        curr.children[index] = child;
      }
      currWord.append(ch);
      if (child.isEnd) {
        longestWords.remove(currWord.toString());
        // so we don't remove again, but it doesn't matter
        // child.isEnd= false;
      }
      curr = child;

    }
    curr.isEnd = true;
    // Different case: em, emit, or emit, em
    if (!curr.hasChild) { // don't forget this BUG
      longestWords.add(word);
    }
  }

  public Set<String> getLongestWords() {
    return longestWords;
  }

  @Override
  public String toString() {
    return "Trie [root=" + root + ", longestWords=" + longestWords + "]";
  }

}
https://leetcode.com/problems/short-encoding-of-words/discuss/125869/Simple-Concept-using-Trie
Firstly, sort the Strings based on their length in the descending order.
For each word in the String insert the word in Trie from right to left. For "time" insert "emit" in Trie.
Notice, that for all the string that are suffixes of another string, you won't need to create a new TrieNode.
Keep track of whether a new node was created when inserting into the trie. If yes, then add this word into the reference String S else continue.
Ex:
words = ["time", "me", "bell"]
Sort in descending order of length
words = ["time", "bell", "me"]
Let ans = 0
Inserting "time" in the Trie, we insert "emit" and create 4 TrieNodes starting from the root. (ans = 4(length of "time")+1(1 for the #))
Similarly, while inserting "bell" we will create new TrieNodes (ans+= 4 + 1)
However, while inserting "me" we already have an edges from root labelled as "e" and a node from "e" labelled as "m" and hence no new trie node is created.
Hence we get ans=10.
    class TrieNode {
        TrieNode[] child = new TrieNode[26]; 
    }
    
    public boolean adder(String word, TrieNode root){
        boolean newNeed = false; 
        for(int i = word.length() -1; i>=0; --i){
            char c = word.charAt(i); 
            if(root.child[c-'a']==null){
                newNeed = true; 
                root.child[c-'a'] = new TrieNode(); 
            }
            root = root.child[c-'a']; 
        }
        return newNeed; 
    }
    
    public int minimumLengthEncoding(String[] words) {
        if(words == null || words.length < 1)return 0; 
        Arrays.sort(words, (a, b)->b.length() - a.length()); 
        TrieNode root = new TrieNode(); 
        int len = 0 ; 
        for(String word : words){
            if(adder(word, root)){
                len+=word.length()+1; 
            }
        }
        return len; 
    }


X. Store Prefixes
https://leetcode.com/problems/short-encoding-of-words/discuss/125811/C++JavaPython-Easy-Understood-Solution-with-Explanation
我们将words中的所有字符串都放在一个哈希表中,然后对于哈希表中的每个单词,在哈希表中删除掉它的所有后缀(因为它的后缀可以由它本身encode得到)。最后再计算出来哈希表中所有单词的长度和即可。
Base on @awice's idea. This solution is not my intuition but it is really simple to write, compared with Trie solution.
  1. Build a set of words.
  2. Iterate on all words and remove all suffixes of every word from the set.
  3. Finally the set will the set of all encoding words.
  4. Iterate on the set and return sum(word's length + 1 for every word in the set)
Complexity
O(NK^2) for time and 'O(NK)' for space.
It is really kind of K with K <= 7, almost ignorable.
I should have suggested for bigger 'K' cases.
I believe it will take more time for most people to solve this problem if we have a big K.

If a word X is a suffix of Y, then it does not need to be considered, as the encoding of Y in the reference string will also encode X. For example, if "me" and "time" is in words, we can throw out "me" without changing the answer.
If a word Y does not have any other word X (in the list of words) that is a suffix of Y, then Y must be part of the reference string.
Thus, the goal is to remove words from the list such that no word is a suffix of another. The final answer would be sum(word.length + 1 for word in words).
Algorithm
Since a word only has up to 7 suffixes (as words[i].length <= 7), let's iterate over all of them. For each suffix, we'll try to remove it from our words list. For efficiency, we'll make words a set.
  • Time Complexity: O(\sum w_i^2), where w_i is the length of words[i].
  • Space Complexity: O(\sum w_i), the space used in storing suffixes.

    public int minimumLengthEncoding(String[] words) {
        Set<String> s = new HashSet<>(Arrays.asList(words));
        for (String w : words)
            for (int i = 1; i < w.length(); ++i)
                s.remove(w.substring(i));
        int res = 0;
        for (String w : s) res += w.length() + 1;
        return res;
    }
X. Pre-Sort
本题考查就是找出一个单词是不是另外一个单词的后缀,如果是的话,就可以Short Encode。所以,我们可以把words中每个单词倒置后排序,然后遍历数组,每个元素只要和其后面相邻的元素比较,如果是后缀则被Short Encode,否则不行。
    def minimumLengthEncoding(self, words):
        reversed_sorted_words = sorted([word[::-1] for word in words])
  
  # The longest word is always present. So include its length
        min_len = len(reversed_sorted_words[-1]) + 1
        
        for i in range(len(reversed_sorted_words)-1):
            
            word = reversed_sorted_words[i]
            next_word = reversed_sorted_words[i+1]            
            
            if not next_word.startswith(word):
                min_len += len(word) + 1
        
        return min_len

X. https://leetcode.com/problems/short-encoding-of-words/discuss/126239/Java-5-lines-no-reverse-using-index-check
 public int minimumLengthEncoding(String[] words) {
    StringBuilder sb = new StringBuilder();
    Arrays.sort(words, (x,y) -> (y.length()- x.length()));
    for(int i = 0; i<words.length; i++)
    {
        if(sb.toString().indexOf(words[i]+"#") == -1)
            sb.append(words[i]).append("#");
    }
    return sb.toString().length();
}
First of all, sort by length in descending order, since the long ones could contain the short ones, but sort ones cannot contains the longs.
Then keep probing to see if next word is in the result or not.
It won't be too efficient though given using the indexOf method, which is scanning the whole string.

X. Brute Force
https://blog.csdn.net/qq2667126427/article/details/80056268
  public int minimumLengthEncoding(String[] words) {
    if (words == null || words.length == 0) {
      return 1;
    }
    // 使用set对字符串去重
    HashSet<String> strings = new HashSet<>();
    // 添加数据,自动去重
    Collections.addAll(strings, words);

    Iterator<String> strs = strings.iterator();
    int size = 0;
    // 全部放入到原来的words中,其实新建一个大小为strings.size()的String数组是
    // 个更好的选择,为了时间不管了,运行中也没有报错。幸好没有要求给出对应的下标数组
    while (strs.hasNext()) {
      words[size++] = strs.next();
    }
    int result = 0, add;
    for (int i = 0; i < size; i++) {
      // 表示本字符串可以给最终字符串增加的长度
      add = words[i].length() + 1;
      for (int j = 0; j < size; j++) {
        // 如果这个字符串是别的字符串的后缀,那么这个字符串就可以用其表示
        // 而不需要在主串后再加一段
        if (i != j && words[j].endsWith(words[i])) {
          add = 0;
          break;
        }
      }
      result += add;
    }
    return result;

  }
https://leetcode.com/problems/short-encoding-of-words/discuss/125822/C%2B%2B-4-lines-reverse-and-sort
If we reverse all strings and then sort the vector, then strings will appear as ["em", "emit", "bell"]. Now we just need to check if the next string starts with the previous one, so we can encode it.
int minimumLengthEncoding(vector<string>& ws, int res = 0) {
    for (auto i = 0; i < ws.size(); ++i) reverse(ws[i].begin(), ws[i].end());
    sort(ws.begin(), ws.end());
    for (auto i = 0; i < ws.size() - 1; ++i) res += ws[i] == ws[i + 1].substr(0, ws[i].size()) ? 0 : ws[i].size() + 1;
    return res + ws[ws.size() - 1].size() + 1;
}
Insted of sorting, we can load reversed string in a set. A benefit of this approach is that we it automatically eliminates duplicate strings. However, it does not work faster than the first solution, probably due to the BST overhead.
I would recommend though using this approach if we expect loads of duplicated strings.
int minimumLengthEncoding(vector<string>& ws, int res = 0) {
    for (auto i = 0; i < ws.size(); ++i) reverse(ws[i].begin(), ws[i].end());
    set<string> s(ws.begin(), ws.end());
    for (auto it = s.begin(); next(it) != s.end(); ++it) res += *it == next(it)->substr(0, it->size()) ? 0 : it->size() + 1;
    return res + s.rbegin()->size() + 1;
}
We can avoid reversing by using a custom comparer, and check endings instead of beginnings. However, the code is more complicated and it does not work faster, given the current test cases (average runtime is 36 ms for both).
I think that this approach will work best if we have a lot of long strings that differts drammatically from each other.
struct reverseComparer {
  bool operator()(const string& a, const string& b) {
    for (auto pa = a.rbegin(), pb = b.rbegin(); pa != a.rend() && pb != b.rend(); ++pa, ++pb) if (*pa != *pb) return *pa < *pb;
    return a.size() < b.size();
  }
};
int minimumLengthEncoding(vector<string>& ws, int res = 0) {
  sort(ws.begin(), ws.end(), reverseComparer());
  for (auto i = 0; i < ws.size() - 1; ++i) res += ws[i].size() <= ws[i + 1].size() 
    && ws[i] == ws[i + 1].substr(ws[i + 1].size() - ws[i].size()) ? 0 : ws[i].size() + 1;
  return res + ws[ws.size() - 1].size() + 1;
}

Labels

LeetCode (1432) GeeksforGeeks (1122) LeetCode - Review (1067) Review (882) Algorithm (668) to-do (609) Classic Algorithm (270) Google Interview (237) Classic Interview (222) Dynamic Programming (220) DP (186) Bit Algorithms (145) POJ (141) Math (137) Tree (132) LeetCode - Phone (129) EPI (122) Cracking Coding Interview (119) DFS (115) Difficult Algorithm (115) Lintcode (115) Different Solutions (110) Smart Algorithm (104) Binary Search (96) BFS (91) HackerRank (90) Binary Tree (86) Hard (79) Two Pointers (78) Stack (76) Company-Facebook (75) BST (72) Graph Algorithm (72) Time Complexity (69) Greedy Algorithm (68) Interval (63) Company - Google (62) Geometry Algorithm (61) Interview Corner (61) LeetCode - Extended (61) Union-Find (60) Trie (58) Advanced Data Structure (56) List (56) Priority Queue (53) Codility (52) ComProGuide (50) LeetCode Hard (50) Matrix (50) Bisection (48) Segment Tree (48) Sliding Window (48) USACO (46) Space Optimization (45) Company-Airbnb (41) Greedy (41) Mathematical Algorithm (41) Tree - Post-Order (41) ACM-ICPC (40) Algorithm Interview (40) Data Structure Design (40) Graph (40) Backtracking (39) Data Structure (39) Jobdu (39) Random (39) Codeforces (38) Knapsack (38) LeetCode - DP (38) Recursive Algorithm (38) String Algorithm (38) TopCoder (38) Sort (37) Introduction to Algorithms (36) Pre-Sort (36) Beauty of Programming (35) Must Known (34) Binary Search Tree (33) Follow Up (33) prismoskills (33) Palindrome (32) Permutation (31) Array (30) Google Code Jam (30) HDU (30) Array O(N) (29) Logic Thinking (29) Monotonic Stack (29) Puzzles (29) Code - Detail (27) Company-Zenefits (27) Microsoft 100 - July (27) Queue (27) Binary Indexed Trees (26) TreeMap (26) to-do-must (26) 1point3acres (25) GeeksQuiz (25) Merge Sort (25) Reverse Thinking (25) hihocoder (25) Company - LinkedIn (24) Hash (24) High Frequency (24) Summary (24) Divide and Conquer (23) Proof (23) Game Theory (22) Topological Sort (22) Lintcode - Review (21) Tree - Modification (21) Algorithm Game (20) CareerCup (20) Company - Twitter (20) DFS + Review (20) DP - Relation (20) Brain Teaser (19) DP - Tree (19) Left and Right Array (19) O(N) (19) Sweep Line (19) UVA (19) DP - Bit Masking (18) LeetCode - Thinking (18) KMP (17) LeetCode - TODO (17) Probabilities (17) Simulation (17) String Search (17) Codercareer (16) Company-Uber (16) Iterator (16) Number (16) O(1) Space (16) Shortest Path (16) itint5 (16) DFS+Cache (15) Dijkstra (15) Euclidean GCD (15) Heap (15) LeetCode - Hard (15) Majority (15) Number Theory (15) Rolling Hash (15) Tree Traversal (15) Brute Force (14) Bucket Sort (14) DP - Knapsack (14) DP - Probability (14) Difficult (14) Fast Power Algorithm (14) Pattern (14) Prefix Sum (14) TreeSet (14) Algorithm Videos (13) Amazon Interview (13) Basic Algorithm (13) Codechef (13) Combination (13) Computational Geometry (13) DP - Digit (13) LCA (13) LeetCode - DFS (13) Linked List (13) Long Increasing Sequence(LIS) (13) Math-Divisible (13) Reservoir Sampling (13) mitbbs (13) Algorithm - How To (12) Company - Microsoft (12) DP - Interval (12) DP - Multiple Relation (12) DP - Relation Optimization (12) LeetCode - Classic (12) Level Order Traversal (12) Prime (12) Pruning (12) Reconstruct Tree (12) Thinking (12) X Sum (12) AOJ (11) Bit Mask (11) Company-Snapchat (11) DP - Space Optimization (11) Dequeue (11) Graph DFS (11) MinMax (11) Miscs (11) Princeton (11) Quick Sort (11) Stack - Tree (11) 尺取法 (11) 挑战程序设计竞赛 (11) Coin Change (10) DFS+Backtracking (10) Facebook Hacker Cup (10) Fast Slow Pointers (10) HackerRank Easy (10) Interval Tree (10) Limited Range (10) Matrix - Traverse (10) Monotone Queue (10) SPOJ (10) Starting Point (10) States (10) Stock (10) Theory (10) Tutorialhorizon (10) Kadane - Extended (9) Mathblog (9) Max-Min Flow (9) Maze (9) Median (9) O(32N) (9) Quick Select (9) Stack Overflow (9) System Design (9) Tree - Conversion (9) Use XOR (9) Book Notes (8) Company-Amazon (8) DFS+BFS (8) DP - States (8) Expression (8) Longest Common Subsequence(LCS) (8) One Pass (8) Quadtrees (8) Traversal Once (8) Trie - Suffix (8) 穷竭搜索 (8) Algorithm Problem List (7) All Sub (7) Catalan Number (7) Cycle (7) DP - Cases (7) Facebook Interview (7) Fibonacci Numbers (7) Flood fill (7) Game Nim (7) Graph BFS (7) HackerRank Difficult (7) Hackerearth (7) Inversion (7) Kadane’s Algorithm (7) Manacher (7) Morris Traversal (7) Multiple Data Structures (7) Normalized Key (7) O(XN) (7) Radix Sort (7) Recursion (7) Sampling (7) Suffix Array (7) Tech-Queries (7) Tree - Serialization (7) Tree DP (7) Trie - Bit (7) 蓝桥杯 (7) Algorithm - Brain Teaser (6) BFS - Priority Queue (6) BFS - Unusual (6) Classic Data Structure Impl (6) DP - 2D (6) DP - Monotone Queue (6) DP - Unusual (6) DP-Space Optimization (6) Dutch Flag (6) How To (6) Interviewstreet (6) Knapsack - MultiplePack (6) Local MinMax (6) MST (6) Minimum Spanning Tree (6) Number - Reach (6) Parentheses (6) Pre-Sum (6) Probability (6) Programming Pearls (6) Rabin-Karp (6) Reverse (6) Scan from right (6) Schedule (6) Stream (6) Subset Sum (6) TSP (6) Xpost (6) n00tc0d3r (6) reddit (6) AI (5) Abbreviation (5) Anagram (5) Art Of Programming-July (5) Assumption (5) Bellman Ford (5) Big Data (5) Code - Solid (5) Code Kata (5) Codility-lessons (5) Coding (5) Company - WMware (5) Convex Hull (5) Crazyforcode (5) DFS - Multiple (5) DFS+DP (5) DP - Multi-Dimension (5) DP-Multiple Relation (5) Eulerian Cycle (5) Graph - Unusual (5) Graph Cycle (5) Hash Strategy (5) Immutability (5) Java (5) LogN (5) Manhattan Distance (5) Matrix Chain Multiplication (5) N Queens (5) Pre-Sort: Index (5) Quick Partition (5) Quora (5) Randomized Algorithms (5) Resources (5) Robot (5) SPFA(Shortest Path Faster Algorithm) (5) Shuffle (5) Sieve of Eratosthenes (5) Strongly Connected Components (5) Subarray Sum (5) Sudoku (5) Suffix Tree (5) Swap (5) Threaded (5) Tree - Creation (5) Warshall Floyd (5) Word Search (5) jiuzhang (5)

Popular Posts