如何在500w個單詞中統計特定字首的單詞有多少個?
阿新 • • 發佈:2018-12-17
之前在公眾號上看到的一個關於演算法的面試題,特在此記錄:
關於演算法的一點記錄
*/ public class DictionaryTree { // 字典樹的節點 private class Node { // 是否是單詞 private boolean isWord; // 單詞計數 private int count; // 字串 private String str; // 子節點 private Map<String, Node> childs; // 父節點 private Node parent; public Node() { childs = new HashMap<String, Node>(); } public Node(boolean isWord, int count, String str) { this(); this.isWord = isWord; this.count = count; this.str = str; } public void addChild(String key, Node node) { childs.put(key, node); node.parent = this; } public void removeChild(String key) { childs.remove(key); } public String toString() { return "str : " + str + ", isWord : " + isWord + ", count : " + count; } } // 字典樹根節點 private Node root; DictionaryTree() { // 初始化root root = new Node(); } // 新增字串 private void addStr(String word, Node node) { // 計數 node.count++; String str = node.str; if(null != str) { // 尋找公共字首 String commonPrefix = ""; for(int i=0; i<word.length(); i++) { if(str.length() > i && word.charAt(i) == str.charAt(i)) { commonPrefix += word.charAt(i); } else { break; } } // 找到公共字首 if(commonPrefix.length() > 0) { if (commonPrefix.length() == str.length() && commonPrefix.length() == word.length()) { // 與之前的詞重複 } else if(commonPrefix.length() == str.length() && commonPrefix.length() < word.length()) { // 剩餘的串 String wordLeft = word.substring(commonPrefix.length()); // 剩餘的串去子節點中繼續找 searchChild(wordLeft, node); } else if(commonPrefix.length() < str.length()) { // 節點裂變 Node splitNode = new Node(true, node.count, commonPrefix); // 處理裂變節點的父關係 splitNode.parent = node.parent; splitNode.parent.addChild(commonPrefix, splitNode); node.parent.removeChild(node.str); node.count--; // 節點裂變後的剩餘字串 String strLeft = str.substring(commonPrefix.length()); node.str = strLeft; splitNode.addChild(strLeft, node); // 單詞裂變後的剩餘字串 if(commonPrefix.length() < word.length()) { splitNode.isWord = false; String wordLeft = word.substring(commonPrefix.length()); Node leftNode = new Node(true, 1, wordLeft); splitNode.addChild(wordLeft, leftNode); } } } else { // 沒有共同字首,直接新增節點 Node newNode = new Node(true, 1, word); node.addChild(word, newNode); } } else { // 根結點 if(node.childs.size() > 0) { searchChild(word, node); } else { Node newNode = new Node(true, 1, word); node.addChild(word, newNode); } } } // 在子節點中新增字串 public void searchChild(String wordLeft, Node node) { boolean isFind = false; if(node.childs.size() > 0) { // 遍歷孩子 for(String childKey : node.childs.keySet()) { Node childNode = node.childs.get(childKey); // 首字母相同,則在該子節點繼續新增字串 if(wordLeft.charAt(0) == childNode.str.charAt(0)) { isFind = true; addStr(wordLeft, childNode); break; } } } // 沒有首字母相同的孩子,則將其變為子節點 if(!isFind) { Node newNode = new Node(true, 1, wordLeft); node.addChild(wordLeft, newNode); } } // 新增單詞 public void add(String word) { addStr(word, root); } // 在節點中查詢字串 private boolean findStr(String word, Node node) { boolean isMatch = true; String wordLeft = word; String str = node.str; if(null != str) { // 字串與單詞不匹配 if(word.indexOf(str) != 0) { isMatch = false; } else { // 匹配,則計算剩餘單詞 wordLeft = word.substring(str.length()); } } // 如果匹配 if(isMatch) { // 如果還有剩餘單詞長度 if(wordLeft.length() > 0) { // 遍歷孩子繼續找 for(String key : node.childs.keySet()) { Node childNode = node.childs.get(key); boolean isChildFind = findStr(wordLeft, childNode); if(isChildFind) { return true; } } return false; } else { // 沒有剩餘單詞長度,說明已經匹配完畢,直接返回節點是否為單詞 return node.isWord; } } return false; } // 查詢單詞 public boolean find(String word) { return findStr(word, root); } // 統計子節點字串單詞數 private int countChildStr(String prefix, Node node) { // 遍歷孩子 for(String key : node.childs.keySet()) { Node childNode = node.childs.get(key); // 匹配子節點 int childCount = countStr(prefix, childNode); if(childCount != 0) { return childCount; } } return 0; } // 統計字串單詞數 private int countStr(String prefix, Node node) { String str = node.str; // 非根結點 if(null != str) { // 字首與字串不匹配 if(prefix.indexOf(str) != 0 && str.indexOf(prefix) != 0) { return 0; // 字首匹配字串,且字首較短 } else if(str.indexOf(prefix) == 0) { // 找到目標節點,返回單詞數 return node.count; // 字首匹配字串,且字串較短 } else if(prefix.indexOf(str) == 0) { // 剩餘字串繼續匹配子節點 String prefixLeft = prefix.substring(str.length()); if(prefixLeft.length() > 0) { return countChildStr(prefixLeft, node); } } } else { // 根結點,直接找其子孫 return countChildStr(prefix, node); } return 0; } // 統計字首單詞數 public int count(String prefix) { // 處理特殊情況 if(null == prefix || prefix.trim().isEmpty()) { return root.count; } // 從根結點往下匹配 return countStr(prefix, root); } // 列印節點 private void printNode(Node node, int layer) { // 層級遞進 for(int i=0; i<layer; i++) { System.out.print("\t"); } // 列印 System.out.println(node); // 遞迴列印子節點 for (String str : node.childs.keySet()) { Node child = node.childs.get(str); printNode(child, layer + 1); } } // 列印字典樹 public void print() { printNode(root, 0); } }
主方法
/** * @author xiaoshi on 2018/10/5. */ public class Main { public static void main(String[] args) { DictionaryTree dt = new DictionaryTree(); dt.add("interest"); dt.add("interesting"); dt.add("interested"); dt.add("inside"); dt.add("insert"); dt.add("apple"); dt.add("inter"); dt.add("interesting"); dt.print(); boolean isFind = dt.find("inside"); System.out.println("find inside : " + isFind); int count = dt.count("inter"); System.out.println("count prefix inter : " + count); } }
執行結果
str : null, isWord : false, count : 8 str : apple, isWord : true, count : 1 str : in, isWord : false, count : 7 str : ter, isWord : true, count : 5 str : est, isWord : true, count : 4 str : ing, isWord : true, count : 2 str : ed, isWord : true, count : 1 str : s, isWord : false, count : 2 str : ert, isWord : true, count : 1 str : ide, isWord : true, count : 1 find inside : true count prefix inter : 5
公眾號貼在下面了: