Tree

Definition

A tree is a (possibly non-linear) data structure made up of nodes or vertices and edges without having any cycle. 

Tree

Tree is one of the most important data structure in algorithms

Like our current File System

Tree has many different types

Tree

Tree

B Tree

Trie Tree

Binary Tree

BST

RB Tree

AVL

Terminology

  • Node
    • Root
    • Leaf
    • Parent
    • Child
    • Siblings
    • Ancestor
    • Descendant
  • Edge
  • Height
  • Depth
  • Level
  • Path

A

B

C

D

E

F

G

J

I

H

Binary Tree

  • It is actually different from tree
  • It has right sub-tree and left sub-tree
  • Each node can have at most two children

Complete Binary Tree

  • Very useful in algorithm problems
  • Always assuming as a tree's property to compute the time complexity

Properties of Binary Tree

  • at Level i, at most 2^i nodes
  • a tree with height k, at most 2^k-1 nodes
  • a complete binary tree with n nodes, the height will be 
  • If we number the node from root and base on the level, for a complete binary tree, we will have:
    • for node number k, the left child is 2k+1
    • for node number k, the right child is 2k+2
  • How to store a Binary Tree?
\left \lceil log_2(n+1) \right \rceil

Basic Data Structure of a Binary Tree

public class BinaryTree<T> {
    private Node<T> root;

    public Tree(T rootData) {
        root = new Node<T>();
        root.data = rootData;
    }

    public static class Node<T> {
        private T data;
        private Node<T> leftNode;
        private Node<T> rightNode;
    }
}

Traversal of a Binary Tree

  • PreOrder: Parent, left child, right child
  • InOrder: left child, Parent, right child
  • PostOrder: left child, right child, Parent

Traversal of a Binary Tree

A

B

D

E

H

I

J

G

F

C

PreOrder: A B D E H I C F G J

InOrder: D B H E I A F C G J

PostOrder: D H I E B F J G C A

Traversal of a Binary Tree

We need to use recursion to traverse a tree

public void preorder(TreeNode root) {  
    if(root !=  null) {  
   //Visit the node by Printing the node data    
      System.out.printf("%c ",root.data);  
      preorder(root.left);  
      preorder(root.right);  
    }  
}  
def preorder(root):
    if root is not None:
        # Visit the node by printing the node data
        print(root.data, end=" ")
        preorder(root.left)
        preorder(root.right)

Traversal of a Binary Tree

public void inorder(TreeNode root) {  
    if(root !=  null) {  
      inorder(root.left);  
      System.out.printf("%c ",root.data);  
      inorder(root.right);  
    }  
}  
public void postorder(TreeNode root) {  
    if(root !=  null) {    
      postorder(root.left);  
      postorder(root.right);
      System.out.printf("%c ",root.data);    
    }  
}  
def inorder(root):
    if root is not None:
        inorder(root.left)
        print(root.data, end=" ")
        inorder(root.right)
def postorder(root):
    if root is not None:
        postorder(root.left)
        postorder(root.right)
        print(root.data, end=" ")

Traversal of a Binary Tree

Can we use Stack to traverse the tree without recursion?

Traversal of a Binary Tree

Pre-order traversal: use a stack to simulate recursion, and print the node when it is first encountered. 

Traversal of a Binary Tree

    def preorderTraversal(self, root: Optional[TreeNode]) -> List[int]:
        
        stack, res = [], []
        
        while stack or root:
            while root:
                stack.append(root)  # first time 
                res.append(root.val)
                root = root.left
            
            if stack:
                top = stack.pop()
                if top.right:
                    root = top.right
        return res 

1

/ \

2   3

/ \      

4   5      

Traversal of a Binary Tree

  def inorderTraversal(self, root: TreeNode) -> List[int]:
        stack = []
        res = []
        while root or stack:
            while root:
                stack.append(root)
                root = root.left
            if stack:
                root = stack.pop()
                res.append(root.val)
                root = root.right
        return res 

Traversal of a Binary Tree

PostOrder: The most difficult one

PostOrder: go to left, when there is no left, visit it. Then for the top of the stack, do we visit it now?

No. Because it is what InOrder does

We need to then go to the right until there is nowhere to go, we visit it and then pop

Traversal of a Binary Tree

PostOrder: The most difficult one

Can we do the PostOrder just using the current structure?

No. We do not know the status of the top element in the stack. Has the right child been traversed or not?

 

Traversal of a Binary Tree

We need an additional flag to store the status of the node

static class NodeWithFlag {
	Node node;
	boolean flag;
	public NodeWithFlag(Node n, boolean value) {
		node = n;
		flag = value;
	}
}

After we visit the right child, we update the flag;

Traversal of a Binary Tree

public static void PostOrder(Node root) {
	Stack<NodeWithFlag> nodeStack = new Stack<NodeWithFlag>();
	Node curNode = root;
	NodeWithFlag newNode;
	while(!nodeStack.empty() || curNode != null) {
		while(curNode != null) {
			newNode = new NodeWithFlag(curNode, false);
			nodeStack.push(newNode);
			curNode = curNode.leftNode;
		}
		newNode = nodeStack.pop();
		curNode = newNode.node;
		if(!newNode.flag) {
			newNode.flag = true;
			nodeStack.push(newNode);
			curNode = curNode.rightNode;
		}
		else {
			System.out.printf("%c ", curNode.data);
			curNode = null;
		}
	}
}

1

/ \

2   3

/ \      

4   5      

Traversal of a Binary Tree

def postorder(root):
    class NodeWithFlag:
        def __init__(self, node, flag):
            self.node = node
            self.flag = flag
    
    node_stack = []
    cur_node = root
    while node_stack or cur_node:
        while cur_node:
            new_node = NodeWithFlag(cur_node, False)
            node_stack.append(new_node)
            cur_node = cur_node.leftNode
        
        new_node = node_stack.pop()
        cur_node = new_node.node
        if not new_node.flag:
            new_node.flag = True
            node_stack.append(new_node)
            cur_node = cur_node.rightNode
        else:
            print(cur_node.data, end=' ')
            cur_node = None

Traversal of a Binary Tree

Another way is to use a HashSet to store which node has been visited

First visit :HashSet.contains() == false -> put into set

Second visit: HashSet.contains == true -> output value

Construct a Binary Tree

If we have a traversal result of a tree, can we construct the tree?

PreOrder: A B C D

A

B

D

C

A

B

D

C

Construct a Binary Tree

No matter if you give a PreOrder, InOrder or PostOrder, it can construct different trees

What if we give two traversals?

Construct a Binary Tree

PreOrder: A B C D

A

B

D

C

A

B

D

C

InOrder: B A D C

Construct a Binary Tree

PreOrder: A B C D

A

B

D

C

PostOrder: B D C A

A

B

D

C

Construct a Binary Tree

Conclusion: You need two traversals to construct a tree, and one of them must be a InOrder traversal

How to construct a tree with a PreOrder and an InOrder?

Construct a Binary Tree

A

B

D

E

H

I

J

G

F

C

PreOrder: A B D E H I C F G J

InOrder: D B H E I A F C G J

A is root so A is the first in PreOrder, InOrder can use A to seperate left sub-tree and right sub-tree 

Then we can do recursion

 public TreeNode buildTree(int[] preorder, int[] inorder) {
        final int preStart = 0;
        final int preEnd = preorder.length - 1;
        final int inStart = 0;
        final int inEnd = inorder.length - 1;

        return constructTree(preorder, preStart, preEnd, inorder, inStart, inEnd);
    }

    private TreeNode constructTree(int[] preorder, int preStart, int preEnd, int[] inorder, int inStart, int inEnd) {
        // Base case: if there are no elements to construct the tree
        if (preStart > preEnd || inStart > inEnd) {
            return null;
        }

        // The first element of preorder is the root of the current subtree
        final int rootValue = preorder[preStart];
        TreeNode root = new TreeNode(rootValue);

        // Find the position of the root in inorder to determine the boundaries of left and right subtrees
        int inorderRootIndex = 0;
        for (int i = inStart; i <= inEnd; i++) {
            if (inorder[i] == rootValue) {
                inorderRootIndex = i;
                break;
            }
        }

        // Number of nodes in the left subtree
        final int leftSubtreeSize = inorderRootIndex - inStart;

        // Recursively construct the left and right subtrees
        root.left = constructTree(preorder, preStart + 1, preStart + leftSubtreeSize, inorder, inStart, inorderRootIndex - 1);
        root.right = constructTree(preorder, preStart + leftSubtreeSize + 1, preEnd, inorder, inorderRootIndex + 1, inEnd);

        return root;
    }

PreOrder + InOrder

def buildTree(preorder, inorder):
    preStart = 0
    preEnd = len(preorder) - 1
    inStart = 0
    inEnd = len(inorder) - 1

    return construct(preorder, preStart, preEnd, inorder, inStart, inEnd)

def construct(preorder, preStart, preEnd, inorder, inStart, inEnd):
    if preStart > preEnd or inStart > inEnd:
        return None

    val = preorder[preStart]
    p = TreeNode(val)
    k = 0
    for i in range(inStart, inEnd + 1):
        if val == inorder[i]:
            k = i
            break

    p.left = construct(preorder, preStart + 1, preStart + (k - inStart), inorder, inStart, k - 1)
    p.right = construct(preorder, preStart + (k - inStart) + 1, preEnd, inorder, k + 1, inEnd)
    return p

Construct a Binary Tree

A

B

D

E

H

I

J

G

F

C

PostOrder: D H I E B F J G C A

InOrder: D B H E I A F C G J

It is similar as PreOrder + InOrder, the only difference is the root now is the last one in PostOrder

PostOrder + InOrder

public TreeNode buildTree(int[] inorder, int[] postorder) {
        int inStart = 0;
        int inEnd = inorder.length - 1;
	int postStart = 0;
        int postEnd = postorder.length - 1;
	return build(inorder, inStart, inEnd, postorder, postStart, postEnd);
}
public TreeNode build(int[] inorder, int inStart, int inEnd, int[] postorder, int postStart, int postEnd) {
	if (inStart > inEnd || postStart > postEnd)
		return null;
 
	int rootValue = postorder[postEnd];
	TreeNode root = new TreeNode(rootValue);
 
	int k = 0;
	for (int i = inStart; i <=inEnd; i++) {
		if (inorder[i] == rootValue) {
			k = i;
			break;
		}
	}
 
	root.left = build(inorder, inStart, k - 1, postorder, postStart,
			postStart + k - (inStart + 1));
	root.right = build(inorder, k + 1, inEnd, postorder, postStart + k- inStart, postEnd - 1);
	return root;
}

PostOrder + InOrder

def buildTree(inorder, postorder):
    inStart = 0
    inEnd = len(inorder) - 1
    postStart = 0
    postEnd = len(postorder) - 1
    return build(inorder, inStart, inEnd, postorder, postStart, postEnd)

def build(inorder, inStart, inEnd, postorder, postStart, postEnd):
    if inStart > inEnd or postStart > postEnd:
        return None

    rootValue = postorder[postEnd]
    root = TreeNode(rootValue)

    k = 0
    for i in range(inStart, inEnd + 1):
        if inorder[i] == rootValue:
            k = i
            break

    root.left = build(inorder, inStart, k - 1, postorder, postStart, postStart + k - (inStart + 1))
    root.right = build(inorder, k + 1, inEnd, postorder, postStart + k - inStart, postEnd - 1)
    return root

Binary Search Tree

30

18

13

24

22

27

47

40

31

34

Binary Search Tree

  • All the sub-tree are BST
  • All elements in left sub-tree is small than root
  • All elements in Right sub-tree is larger than root
  • If we do InOrder traversal, the result of BST is a sorted array

Binary Search Tree

  • Find
  • Add
  • Remove

Binary Search Tree

30

18

13

24

22

27

47

40

31

34

Find 24

  • 30 > 24 -> go to left
  • 18 < 24 -> go to right
  • find 24, return true

Find 42

  • 30 < 42 -> go to right
  • 34 < 42 -> go to right 
  • 40 < 42 -> go to right
  • 47 > 42 -> go to left
  • nothing on left, return false

Binary Search Tree

public static boolean find(int value, Node root) {
	Node node = root;
	while(node != null) {
		if(node.data > value) {
			node = node.leftNode;
		}
		else if(node.data < value) {
			node = node.rightNode;
		}
		else return true;
	}
	return false;
}
def find(value, root):
    node = root
    while node is not None:
        if node.data > value:
            node = node.leftNode
        elif node.data < value:
            node = node.rightNode
        else:
            return True
    return False

Binary Search Tree

30

18

13

24

22

27

47

40

31

34

Add 42

  • First we need to make sure if there is 42 in the tree already
  • Find the place we put 42

Binary Search Tree

30

18

13

24

22

27

47

40

31

34

30

18

13

24

22

27

47

40

31

34

42

Binary Search Tree

public static boolean add(int value, Node root) {
	if(root == null) {
		root = new Node(value);
		return true;
	}
	Node node = root;
	while(node != null) {
		if(node.data > value) {
			if(node.leftNode != null) {
				node = node.leftNode;
			}
			else {
				node.leftNode = new Node(value);
				return true;
			}
		}
		else if(node.data < value) {
			if(node.rightNode != null) {
				node = node.rightNode;
			}
			else {
				node.rightNode = new Node(value);
				return true;
			}
		}
		else return false;
	}
	return false;
}
def add(value, root):
    if root is None:
        root = Node(value)
        return True
    
    node = root
    while node is not None:
        if node.data > value:
            if node.leftNode is not None:
                node = node.leftNode
            else:
                node.leftNode = Node(value)
                return True
        elif node.data < value:
            if node.rightNode is not None:
                node = node.rightNode
            else:
                node.rightNode = Node(value)
                return True
        else:
            return False
    return False

Binary Search Tree

What is the time complexity for finding and adding some element into the tree?

Binary Search Tree

30

18

13

24

22

27

47

40

31

34

Remove 27

  • First we need to make sure if there is 27 in the tree already
  • We need to remove 27

Remove 30

  • How do we remove something that is not a leaf?

Binary Search Tree

  • If Q is a leaf
  • If Q has one child
    • if the child R is the right child
    • if the child R is the left child
  • if Q has two children

Binary Search Tree

P

Q

R

P

Q

R

P

Q

R

P

Q

R

P<R<Q

P<Q<R

R<Q<P

Q<R<P

Just use R to replace Q

Binary Search Tree

P

Q

R1

R2

if we remove Q, which is the best substitute?

Binary Search Tree

P

Q

R1

R2

Consider the inOrder traversal, the one just before Q and the one just after Q are the best candidates, since if they replace Q, when we do the inOrder again, the output is still an sorted array, which means the tree is still a BST.

So where are these two nodes?

Binary Search Tree

P

Q

R1

R2

So for the element before Q, it is the largest node in sub-tree R1

The element after Q, it is the smallest node in sub-tree R2

Does that node have child? Canwe remove them directly

Maybe, but at most one, so we go back to condition 2

public static boolean remove(int value, Node root) {
	if(root == null) return false;
	if(root.data == value) {
		root = removeNode(root);
		return true;
	}
	Node node = root;
	while(node != null) {
		if(node.data > value) {
			if(node.leftNode != null && node.leftNode.data != value) {
				node = node.leftNode;
			}
			else if(node.leftNode == null) return false;
			else {
				node.leftNode = removeNode(node.leftNode);
				return true;
			}
		}
		else if(node.data < value) {
			if(node.rightNode != null && node.rightNode.data != value) {
				node = node.rightNode;
			}
			else if(node.rightNode == null) return false;
			else {
				node.rightNode = removeNode(node.rightNode);
				return true;
			}
		}
		else return false;
	}
	return false;
}

Binary Search Tree(cont)

public static Node removeNode(Node node) {
	if(node.leftNode == null && node.rightNode == null) {
		return null;
	}
	else if(node.leftNode == null) {
		return node.rightNode;
	}
	else if(node.rightNode == null) {
		return node.leftNode;
	}
	else {
		node.data = findAndRemove(node);
		return node;
	}
}

public static int findAndRemove(Node node) {
	int result;
	if(node.leftNode.rightNode == null) {
		result = node.leftNode.data;
		node.leftNode = node.leftNode.leftNode;
		return result;
	}
	node = node.leftNode;
	while(node.rightNode.rightNode != null) {
		node = node.rightNode;
	}
	result = node.rightNode.data;
	node.rightNode = node.rightNode.leftNode;
	return result;
}
def remove(value, root):
    if root is None:
        return False
    if root.data == value:
        root = removeNode(root)
        return True
    
    node = root
    while node is not None:
        if node.data > value:
            if node.leftNode is not None and node.leftNode.data != value:
                node = node.leftNode
            elif node.leftNode is None:
                return False
            else:
                node.leftNode = removeNode(node.leftNode)
                return True
        elif node.data < value:
            if node.rightNode is not None and node.rightNode.data != value:
                node = node.rightNode
            elif node.rightNode is None:
                return False
            else:
                node.rightNode = removeNode(node.rightNode)
                return True
        else:
            return False
    return False

Binary Search Tree(cont)

def removeNode(node):
    if node.leftNode == None and node.rightNode == None:
        return None
    elif node.leftNode == None:
        return node.rightNode
    elif node.rightNode == None:
        return node.leftNode
    else:
        node.data = findAndRemove(node)
        return node

def findAndRemove(node):
    result = 0
    if node.leftNode.rightNode == None:
        result = node.leftNode.data
        node.leftNode = node.leftNode.leftNode
        return result
    node = node.leftNode
    while node.rightNode.rightNode != None:
        node = node.rightNode
    result = node.rightNode.data
    node.rightNode = node.rightNode.leftNode
    return result

Binary Search Tree

30

18

13

24

22

27

47

40

31

34

42

43

30

18

13

24

22

27

47

43

34

40

42

31

Binary Search Tree

We know the search time is highly related to the height of the tree

If we keep add and remove elements in the tree, the tree will become unbalanced 

So we have Red-black tree and AVL tree, they could use rotation and reconstruct to make the tree balance.

Trie Tree

Trie Tree

Trie Tree

In computer science, a trie, also called digital tree and sometimes radix tree or prefix tree (as they can be searched by prefixes), is an ordered tree data structure that is used to store a dynamic set or associative array where the keys are usually strings.

What Trie Tree Can Help?

  • It can store a dictionary in a good way without consuming much additional space
  • It can easily find common prefix for two strings (this is very useful in typeahead)
  • It can use easily find if a word is in the dictionary (maintain the order)

Complexity Analysis

In the insertion and finding notice that lowering a single level in the tree is done in constant time, and every time that the program lowers a single level in the tree, a single character is cut from the string.

we can conclude that every function lowers L levels on the tree and every time that the function lowers a level on the tree, it is done in constant time, then the insertion and finding of a word in a trie can be done in O(L) time.

The memory used in the tries depends on the methods to store the edges and how many words have prefixes in common.

Implement Trie (Prefix Tree)

Implement a trie with insert, search, and startsWith methods

We only use lowercase 'a' to 'z'

How many children does a node have?

Implement Trie (Prefix Tree)

class TrieNode {
    // Initialize your data structure here.
	boolean isWord;
	TrieNode[] children;
    public TrieNode() {
        children = new TrieNode[26];
        isWord = false;
    }
}
class TrieNode:
    def __init__(self):
        self.isWord = False
        self.children = [None] * 26

Implement Trie (Prefix Tree)

public class Trie {
    private TrieNode root;

    public Trie() {
        root = new TrieNode();
    }

    // Inserts a word into the trie.
    public void insert(String word) {
        if(word == null || word.length() == 0) return;
        TrieNode pNode = root;
        for(int i = 0; i < word.length(); i ++) {
        	char c = word.charAt(i);
        	int index = c - 'a';
        	if(pNode.children[index] == null) {
        		TrieNode newNode = new TrieNode();
        		pNode.children[index] = newNode;
        	}
        	pNode = pNode.children[index];
        }
        pNode.isWord = true;
    }
}

Implement Trie (Prefix Tree)

class Trie:
    def __init__(self):
        self.root = TrieNode()

    def insert(self, word):
        if word is None or len(word) == 0:
            return
        pNode = self.root
        for i in range(len(word)):
            c = word[i]
            index = ord(c) - ord('a')
            if pNode.children[index] is None:
                newNode = TrieNode()
                pNode.children[index] = newNode
            pNode = pNode.children[index]
        pNode.isWord = True

Implement Trie (Prefix Tree)

// Returns if the word is in the trie.
public boolean search(String word) {
    TrieNode pNode = root;
    if(word == null || word.length() == 0) return true;
    for(int i = 0; i < word.length(); i ++) {
        int index = word.charAt(i) - 'a';
        pNode = pNode.children[index];
        if(pNode == null) return false;
    }
    return pNode.isWord;
}

// Returns if there is any word in the trie that starts with the given prefix.
public boolean startsWith(String prefix) {
    TrieNode pNode = root;
    if(prefix == null || prefix.length() == 0) return true;
    for(int i = 0; i < prefix.length(); i ++) {
        int index = prefix.charAt(i) - 'a';
        pNode = pNode.children[index];
        if(pNode == null) return false;
    }
    return true;
}

Implement Trie (Prefix Tree)

def search(self, word):
    pNode = self.root
    if word is None or len(word) == 0:
        return True
    for i in range(len(word)):
        index = ord(word[i]) - ord('a')
        pNode = pNode.children[index]
        if pNode is None:
            return False
    return pNode.isWord

def startsWith(self, prefix):
    pNode = self.root
    if prefix is None or len(prefix) == 0:
        return True
    for i in range(len(prefix)):
        index = ord(prefix[i]) - ord('a')
        pNode = pNode.children[index]
        if pNode is None:
            return False
    return True

Design In-Memory File System

Design an in-memory file system to simulate the following functions:

ls: Given a path in string format. If it is a file path, return a list that only contains this file's name. If it is a directory path, return the list of file and directory names in this directory. Your output (file and directory names together) should in lexicographic order.

mkdir: Given a directory path that does not exist, you should make a new directory according to the path. If the middle directories in the path don't exist either, you should create them as well. This function has void return type.

addContentToFile: Given a file path and file content in string format. If the file doesn't exist, you need to create that file containing given content. If the file already exists, you need to append given content to original content. This function has void return type.

readContentFromFile: Given a file path, return its content in string format.

Design In-Memory File System

We mentioned File System is a tree.

And file system can have many children under each node. So using Trie tree is a good way to represent it.

class TrieNode {
    // Initialize your data structure here.
    boolean isFile;
    String content;
    Map<String, TrieNode> children;
    public TrieNode() {
        isFile = false;
        children = new HashMap<>();
    }
}
class TrieNode:
    def __init__(self):
        self.isFile = False
        self.content = ""
        self.children = {}

Design In-Memory File System

class FileSystem {
    TrieNode root;
    public FileSystem() {
        root = new TrieNode();
    }
    public List<String> ls(String path) {
        List<String> results = new ArrayList<>();
        TrieNode cur = root;
        String[] routes = path.split("/");
        for (int i = 1; i < routes.length; i ++) {
            cur = cur.children.get(routes[i]);
        }
        if (cur.isFile) {
            results.add(routes[routes.length - 1]);
        } else {
            results.addAll(cur.children.keySet());
            Collections.sort(results);
        }
        return results;
    }
    public void mkdir(String path) {
        getCurrentNode(path);
    }
}

Design In-Memory File System

class FileSystem {
    TrieNode root;
    public FileSystem() {
        root = new TrieNode();
    }
    public void addContentToFile(String filePath, String content) {
        TrieNode cur = getCurrentNode(filePath);
        cur.isFile = true;
        if (cur.content == null) {
            cur.content = new String(content);
        } else {
            cur.content = cur.content.concat(content);
        }
    }
    public String readContentFromFile(String filePath) {
        TrieNode cur = getCurrentNode(filePath);
        return cur.content;
    }
    private TrieNode getCurrentNode(String path) {
        TrieNode cur = root;
        String[] routes = path.split("/");
        for (int i = 1; i < routes.length; i ++) {
            if (!cur.children.containsKey(routes[i])) {
                cur.children.put(routes[i], new TrieNode());
            }
            cur = cur.children.get(routes[i]);
        }
        return cur;
    }
}

Design In-Memory File System

class FileSystem:
    def __init__(self):
        self.root = TrieNode()

    def ls(self, path):
        results = []
        cur = self.root
        routes = path.split("/")
        for i in range(1, len(routes)):
            cur = cur.children.get(routes[i])
        if cur.isFile:
            results.append(routes[-1])
        else:
            results.extend(cur.children.keys())
            results.sort()
        return results

    def mkdir(self, path):
        self.get_current_node(path)

Design In-Memory File System

class FileSystem:
    def __init__(self):
        self.root = TrieNode()

    def addContentToFile(self, filePath, content):
        cur = self.get_current_node(filePath)
        cur.isFile = True
        if cur.content is None:
            cur.content = content
        else:
            cur.content += content

    def readContentFromFile(self, filePath):
        cur = self.get_current_node(filePath)
        return cur.content

    def get_current_node(self, path):
        cur = self.root
        routes = path.split("/")
        for i in range(1, len(routes)):
            if routes[i] not in cur.children:
                cur.children[routes[i]] = TrieNode()
            cur = cur.children[routes[i]]
        return cur

Word Search II

Given an m x n board of characters and a list of strings words, return all words on the board.

Each word must be constructed from letters of sequentially adjacent cells, where adjacent cells are horizontally or vertically neighboring. The same letter cell may not be used more than once in a word.

Example:
Input:
board = [["o","a","a","n"],["e","t","a","e"],["i","h","k","r"],["i","f","l","v"]]
words = ["oath","pea","eat","rain"]
Output: ["eat","oath"]

Word Search II

class TrieNode {
    TrieNode[] children;
    boolean isWord;
    String word;
    public TrieNode() {
        children = new TrieNode[26];
        isWord = false;
        word = null;
    }
}

class Trie {
    TrieNode root;
    public Trie() {
        root = new TrieNode();
    }
    public void insert(String word) {
        TrieNode cur = root;
        for (int i = 0; i < word.length(); i ++) {
            int c = word.charAt(i) - 'a';
            if (cur.children[c] == null) {
                cur.children[c] = new TrieNode();
            }
            cur = cur.children[c];
        }
        cur.isWord = true;
        cur.word = word;
    }
}

Word Search II

class Solution {
    int[] dx = {1, 0, -1, 0};
    int[] dy = {0, 1, 0, -1};
    public List<String> findWords(char[][] board, String[] words) {
        Trie trie = new Trie();
        for (int i = 0; i < words.length; i ++) {
            trie.insert(words[i]);
        }
        int m = board.length;
        int n = board[0].length;
        boolean[][] visited = new boolean[m][n];
        Set<String> results = new HashSet<>();
        for (int i = 0; i < m; i ++) {
            for (int j = 0; j < n; j ++) {
                int c = board[i][j] - 'a';
                if (trie.root.children[c] != null) {
                    search(trie.root.children[c], board, i, j, visited, results);
                }
            }
        }
        return new ArrayList<>(results);
    }
}

Word Search II

void search(TrieNode trienode, char[][] board, int x, int y, boolean[][] visited, Set<String> results) {
    int m = board.length;
    int n = board[0].length;
    if (trienode.isWord) {
        results.add(trienode.word);
    }
    visited[x][y] = true;
    for (int i = 0; i < 4; i ++) {
        int nx = x + dx[i];
        int ny = y + dy[i];
        if (nx >= 0 && nx < m && ny >= 0 && ny < n && !visited[nx][ny]) {
            int c = board[nx][ny] - 'a';
            if (trienode.children[c] != null) {
                search(trienode.children[c], board, nx, ny, visited, results);
            }
        }
    }
    visited[x][y] = false;
} 

Word Search II

class TrieNode:
    def __init__(self):
        self.children = [None] * 26
        self.isWord = False
        self.word = None

class Trie:
    def __init__(self):
        self.root = TrieNode()
    
    def insert(self, word):
        cur = self.root
        for char in word:
            c = ord(char) - ord('a')
            if not cur.children[c]:
                cur.children[c] = TrieNode()
            cur = cur.children[c]
        cur.isWord = True
        cur.word = word

Word Search II

class Solution:
    def findWords(self, board: List[List[str]], words: List[str]) -> List[str]:
        trie = Trie()
        for word in words:
            trie.insert(word)
        
        m = len(board)
        n = len(board[0])
        visited = [[False for _ in range(n)] for _ in range(m)]
        results = set()
        
        def search(trienode, x, y):
            nonlocal m, n
            if trienode.isWord:
                results.add(trienode.word)
            visited[x][y] = True
            for dx, dy in [(1, 0), (0, 1), (-1, 0), (0, -1)]:
                nx, ny = x + dx, y + dy
                if 0 <= nx < m and 0 <= ny < n and not visited[nx][ny]:
                    c = ord(board[nx][ny]) - ord('a')
                    if trienode.children[c]:
                        search(trienode.children[c], nx, ny)
            visited[x][y] = False
        
        for i in range(m):
            for j in range(n):
                c = ord(board[i][j]) - ord('a')
                if trie.root.children[c]:
                    search(trie.root.children[c], i, j)
        
        return list(results)

Other Kinds of Tries

We used the tries to store words with lowercase letters, but the tries can be used to store many other things. We can use bits or bytes instead of lowercase letters and every data type can be stored in the tree, not only strings.

Homework