leetcode of algorithm interview in Dachang 22. Dictionary tree

leetcode of algorithm interview in Dachang 22. Dictionary tree

Video Explanation (efficient learning): Click to learn

catalog:

1. Introduction

2. Time and space complexity

3. Dynamic planning

4. Greed

5. Binary search

6. Depth first & breadth first

7. Double pointer

8. Sliding window

9. Bit operation

10. Recursion & divide and conquer

11 Pruning & backtracking

12. Reactor

13. Monotone stack

14. Sorting algorithm

15. Linked list

16.set&map

17. Stack

18. Queue

19. Array

20. String

21. Trees

22. Dictionary tree

23. Consolidation

24. Other types of questions

Trie tree, namely dictionary tree, also known as prefix tree, is a tree structure. Its typical application is to count and sort a large number of strings (but not limited to strings), so it is often used by search engines for text word frequency statistics. Its priority is to minimize unnecessary string comparison and improve search efficiency.

The core idea of Trie is to exchange space for time, and use the public prefix of string to reduce the overhead of query time, so as to improve efficiency

Basic properties

  • The root node does not contain characters, and each node contains only one character except the following node
  • From the root node to a node, the characters passing through the path are connected to the corresponding string of the node
  • All child nodes of each node contain different characters

Practical applications, such as search

208. Implement Trie (prefix tree)(medium)

  • Idea: the length of this character set is 26, that is, 26 lowercase English letters. isEnd indicates whether the node is the end of the string.
    1. Insert string: start from the root node of the field tree. If the child node exists, continue to process the next character. If the child node does not exist, create a child node to the corresponding position of children, move backward along the pointer, and process the next character. Take inserting 'cad' as an example
    2. Find prefix: starting from the root node, if the child node exists, continue to search the next child node along the pointer until the last one. If all characters of the prefix are found, it indicates that the dictionary tree contains the prefix. If the child node does not exist, the prefix is not included in the dictionary tree, and false is returned.
    3. Lookup string: the same as the lookup prefix, except that the isEnd of the last returned node is true, that is, the string is just a branch of the dictionary tree
  • Complexity analysis: time complexity, initialization as O(1), other operations as O(S), and s as the length of the string. The space complexity is O(T), and t is the size of the character set. This problem is 26

js:

var Trie = function() {
    this.children = {};
};

Trie.prototype.insert = function(word) {
    let nodes = this.children;
    for (const ch of word) {//Circular word
        if (!nodes[ch]) {//If the current character is not in the child node, a response position from the child node to the children is created
            nodes[ch] = {};
        }
        nodes = nodes[ch];//Moves the pointer to the next character child node
    }
    nodes.isEnd = true;//End of character
};

Trie.prototype.searchPrefix = function(prefix) {
    let nodes = this.children;
    for (const ch of prefix) {//Cyclic prefix
        if (!nodes[ch]) {//The current character is not in the child node and returns false directly
            return false;
        }
        nodes = nodes[ch];//Moves the pointer to the next character child node
    }
    return nodes;//Returns the last node
}

Trie.prototype.search = function(word) {
    const nodes = this.searchPrefix(word);
  	//Judge whether the node returned by searchPrefix is the character at the end of the string
    return nodes !== undefined && nodes.isEnd !== undefined;
};

Trie.prototype.startsWith = function(prefix) {
    return this.searchPrefix(prefix);
};

Java:

//java
class Trie {
    private Trie[] children;
    private boolean isEnd;

    public Trie() {
        children = new Trie[26];
        isEnd = false;
    }
    
    public void insert(String word) {
        Trie node = this;
        for (int i = 0; i < word.length(); i++) {
            char ch = word.charAt(i);
            int index = ch - 'a';
            if (node.children[index] == null) {
                node.children[index] = new Trie();
            }
            node = node.children[index];
        }
        node.isEnd = true;
    }
    
    public boolean search(String word) {
        Trie node = searchPrefix(word);
        return node != null && node.isEnd;
    }
    
    public boolean startsWith(String prefix) {
        return searchPrefix(prefix) != null;
    }

    private Trie searchPrefix(String prefix) {
        Trie node = this;
        for (int i = 0; i < prefix.length(); i++) {
            char ch = prefix.charAt(i);
            int index = ch - 'a';
            if (node.children[index] == null) {
                return null;
            }
            node = node.children[index];
        }
        return node;
    }
}

212. Word search II (hard)

  • Idea: add all strings in the words array to the Trie, then traverse the grid, judge whether the string formed by the grid path is in the Trie, and then keep backtracking in the up, down, left and right directions.
  • Complexity analysis: time complexity O(MN ⋅ 3^L), space complexity O(max(MN, KL)), visited space O(MN), dictionary tree O(KL), l is the length of the longest string, and K is the length of the words array. The maximum depth of dfs recursive stack is O(min(L,MN)),
Method 1.Trie

Js:

var findWords = function (board, words) {
    const trie = new Trie();
    const dxys = [
        [0, -1],
        [-1, 0],
        [0, 1],
        [1, 0],
    ];
    const xLen = board.length,
        yLen = board[0].length;
    const visited = {};
    const ret = [];

    // Build Trie
    for (let word of words) {
        trie.insert(word);
    }

    // DFS board
    const dfs = (x, y, nodes, str) => {
        if (nodes[board[x][y]].isEnd) {
            ret.push(str + board[x][y]);
            // Set to false to prevent repetition and add the string to ret
            nodes[board[x][y]].isEnd = false;
        }

        // Process the status of this layer
        nodes = nodes[board[x][y]];
        str += board[x][y];

        // Search in four directions
        visited[x * 100 + y] = true;
        for (let [dx, dy] of dxys) {
            const newX = x + dx,
                newY = y + dy;

            if (
                newX < 0 ||
                newY < 0 ||
                newX >= xLen ||
                newY >= yLen ||
                !nodes[board[newX][newY]] ||
                visited[newX * 100 + newY]
            )
                continue;

            dfs(newX, newY, nodes, str);
        }
        visited[x * 100 + y] = false;
    };

    for (let x = 0; x < xLen; x++) {
        for (let y = 0; y < yLen; y++) {
            if (trie.children[board[x][y]]) dfs(x, y, trie.children, "");
        }
    }

    return ret;
};

var Trie = function () {
    this.children = {};
};

Trie.prototype.insert = function (word) {
    let nodes = this.children;
    for (const ch of word) {//Circular word
        if (!nodes[ch]) {//If the current character is not in the child node, a response position from the child node to the children is created
            nodes[ch] = {};
        }
        nodes = nodes[ch];//Moves the pointer to the next character
    }
    nodes.isEnd = true;//End of character
};


Java:

class Solution {
    int[][] dirs = {{1, 0}, {-1, 0}, {0, 1}, {0, -1}};

    public List<String> findWords(char[][] board, String[] words) {
        Trie trie = new Trie();
        for (String word : words) {
            trie.insert(word);
        }

        Set<String> ans = new HashSet<String>();
        for (int i = 0; i < board.length; ++i) {
            for (int j = 0; j < board[0].length; ++j) {
                dfs(board, trie, i, j, ans);
            }
        }

        return new ArrayList<String>(ans);
    }

    public void dfs(char[][] board, Trie now, int i1, int j1, Set<String> ans) {
        if (!now.children.containsKey(board[i1][j1])) {
            return;
        }
        char ch = board[i1][j1];
        now = now.children.get(ch);
        if (!"".equals(now.word)) {
            ans.add(now.word);
        }

        board[i1][j1] = '#';
        for (int[] dir : dirs) {
            int i2 = i1 + dir[0], j2 = j1 + dir[1];
            if (i2 >= 0 && i2 < board.length && j2 >= 0 && j2 < board[0].length) {
                dfs(board, now, i2, j2, ans);
            }
        }
        board[i1][j1] = ch;
    }
}

class Trie {
    String word;
    Map<Character, Trie> children;
    boolean isWord;

    public Trie() {
        this.word = "";
        this.children = new HashMap<Character, Trie>();
    }

    public void insert(String word) {
        Trie cur = this;
        for (int i = 0; i < word.length(); ++i) {
            char c = word.charAt(i);
            if (!cur.children.containsKey(c)) {
                cur.children.put(c, new Trie());
            }
            cur = cur.children.get(c);
        }
        cur.word = word;
    }
}

720. The longest word in the dictionary (easy)

Method 1: sort+hash
  • Idea: sort the array, and then traverse the string array to judge whether the substrings of each string in the array are in the array
  • Complexity: time complexity O(mn), M is the length of string array, and n is the maximum length of string. Spatial complexity O(m)

js:

var longestWord = function (words) {
    let set = new Set()
    words.forEach(v => set.add(v))//set is easy to find
		//Sort by length first, then by dictionary
    words.sort((a, b) => a.length === b.length ? a.localeCompare(b) : b.length - a.length)
    for (let i = 0; i < words.length; i++) {
        let flag = true
        for (let j = 1; j < words[i].length; j++) {
            if (!set.has(words[i].substring(0, j))) {//Check whether there is each substring of the string in the set
                flag = false
                break
            }
        }
        if (flag) {
            return words[i]
        }
    }
    return ''
};


java:

class Solution {
    public String longestWord(String[] words) {
        Set<String> wordset = new HashSet();
        for (String word: words) wordset.add(word);
        Arrays.sort(words, (a, b) -> a.length() == b.length()
                    ? a.compareTo(b) : b.length() - a.length());
        for (String word: words) {
            boolean flag = true;
            for (int k = 1; k < word.length(); ++k) {
                if (!wordset.contains(word.substring(0, k))) {
                    flag = false;
                    break;
                }
            }
            if (flag) return word;
        }

        return "";
    }
}
Method 2: dictionary tree

  • Idea: insert all strings into trie and recursively find the word with the largest length
  • Complexity: time complexity O(mn), m is the length of string array, and n is the maximum length of string. Spatial complexity O(∑ w). The recursion depth will not exceed the maximum word length, and the spatial complexity of the field book is the sum of the lengths of all strings.

js:

var longestWord = function (words) {
    const trie = new Trie()
    words.forEach(word => {//Insert all strings into trie
        trie.insert(word)
    })
    let res = ''
    const _helper = (nodes, path) => {
        if (path.length > res.length || (res.length === path.length && res > path)) {
            res = path
        }
				//{a:{b1:{c1:{isEnd: true}},b2:{c2:{isEnd: true}}}}
        for (const [key, value] of Object.entries(nodes)) {        
            if (value.isEnd) {//If the current character is the end of a string
                path += key
                _helper(value, path)//Recursive search
                path = path.slice(0, -1)//to flash back
            }
        }
    }
    _helper(trie.children, '')//Recursively find the word with the largest length
    return res
}

var Trie = function() {
    this.children = {};
};

Trie.prototype.insert = function(word) {
    let nodes = this.children;
    for (const ch of word) {//Circular word
        if (!nodes[ch]) {//If the current character is not in the child node, a response position from the child node to the children is created
            nodes[ch] = {};
        }
        nodes = nodes[ch];//Moves the pointer to the next character
    }
    nodes.isEnd = true;//End of character
};



Keywords: leetcode

Added by slicer123 on Wed, 08 Dec 2021 10:26:13 +0200