Skip to content

Trees

Definition

Trees Definition


Tree Types

Tree Types


Big O Complexity

Trees Complexity


When to Use

Trees When to Use


Binary Search Tree Implementation

public class BST<T extends Comparable<T>> {

    class TreeNode {
        T val;
        TreeNode left, right;
        TreeNode(T val) { this.val = val; }
    }

    private TreeNode root;

    // Insert - O(log n) average
    public void insert(T val) {
        root = insertRec(root, val);
    }

    private TreeNode insertRec(TreeNode node, T val) {
        if (node == null) return new TreeNode(val);

        if (val.compareTo(node.val) < 0) {
            node.left = insertRec(node.left, val);
        } else if (val.compareTo(node.val) > 0) {
            node.right = insertRec(node.right, val);
        }
        return node;
    }

    // Search - O(log n) average
    public boolean search(T val) {
        return searchRec(root, val);
    }

    private boolean searchRec(TreeNode node, T val) {
        if (node == null) return false;
        if (val.equals(node.val)) return true;

        return val.compareTo(node.val) < 0
            ? searchRec(node.left, val)
            : searchRec(node.right, val);
    }

    // Delete - O(log n) average
    public void delete(T val) {
        root = deleteRec(root, val);
    }

    private TreeNode deleteRec(TreeNode node, T val) {
        if (node == null) return null;

        if (val.compareTo(node.val) < 0) {
            node.left = deleteRec(node.left, val);
        } else if (val.compareTo(node.val) > 0) {
            node.right = deleteRec(node.right, val);
        } else {
            // Node to delete found
            if (node.left == null) return node.right;
            if (node.right == null) return node.left;

            // Two children: get in-order successor
            node.val = findMin(node.right);
            node.right = deleteRec(node.right, node.val);
        }
        return node;
    }

    private T findMin(TreeNode node) {
        while (node.left != null) node = node.left;
        return node.val;
    }

    // Traversals
    public void inorder(TreeNode node, List<T> result) {
        if (node == null) return;
        inorder(node.left, result);
        result.add(node.val);
        inorder(node.right, result);
    }

    public void preorder(TreeNode node, List<T> result) {
        if (node == null) return;
        result.add(node.val);
        preorder(node.left, result);
        preorder(node.right, result);
    }

    public void postorder(TreeNode node, List<T> result) {
        if (node == null) return;
        postorder(node.left, result);
        postorder(node.right, result);
        result.add(node.val);
    }

    // Level-order (BFS)
    public List<List<T>> levelOrder() {
        List<List<T>> result = new ArrayList<>();
        if (root == null) return result;

        Queue<TreeNode> queue = new LinkedList<>();
        queue.offer(root);

        while (!queue.isEmpty()) {
            int levelSize = queue.size();
            List<T> level = new ArrayList<>();

            for (int i = 0; i < levelSize; i++) {
                TreeNode node = queue.poll();
                level.add(node.val);
                if (node.left != null) queue.offer(node.left);
                if (node.right != null) queue.offer(node.right);
            }
            result.add(level);
        }
        return result;
    }
}

Heap Implementation

public class MinHeap {
    private int[] heap;
    private int size;
    private int capacity;

    public MinHeap(int capacity) {
        this.capacity = capacity;
        this.heap = new int[capacity];
        this.size = 0;
    }

    private int parent(int i) { return (i - 1) / 2; }
    private int leftChild(int i) { return 2 * i + 1; }
    private int rightChild(int i) { return 2 * i + 2; }

    // Insert - O(log n)
    public void insert(int val) {
        if (size == capacity) resize();
        heap[size] = val;
        heapifyUp(size);
        size++;
    }

    // Extract min - O(log n)
    public int extractMin() {
        if (size == 0) throw new NoSuchElementException();
        int min = heap[0];
        heap[0] = heap[size - 1];
        size--;
        heapifyDown(0);
        return min;
    }

    // Peek - O(1)
    public int peek() {
        if (size == 0) throw new NoSuchElementException();
        return heap[0];
    }

    private void heapifyUp(int i) {
        while (i > 0 && heap[parent(i)] > heap[i]) {
            swap(i, parent(i));
            i = parent(i);
        }
    }

    private void heapifyDown(int i) {
        int smallest = i;
        int left = leftChild(i);
        int right = rightChild(i);

        if (left < size && heap[left] < heap[smallest]) smallest = left;
        if (right < size && heap[right] < heap[smallest]) smallest = right;

        if (smallest != i) {
            swap(i, smallest);
            heapifyDown(smallest);
        }
    }

    private void swap(int i, int j) {
        int temp = heap[i];
        heap[i] = heap[j];
        heap[j] = temp;
    }

    private void resize() {
        capacity *= 2;
        heap = Arrays.copyOf(heap, capacity);
    }
}

// Using Java's PriorityQueue
PriorityQueue<Integer> minHeap = new PriorityQueue<>();
PriorityQueue<Integer> maxHeap = new PriorityQueue<>(Collections.reverseOrder());

Trie Implementation

public class Trie {

    private class TrieNode {
        Map<Character, TrieNode> children = new HashMap<>();
        boolean isEndOfWord = false;
    }

    private TrieNode root;

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

    // Insert - O(m)
    public void insert(String word) {
        TrieNode node = root;
        for (char c : word.toCharArray()) {
            node.children.putIfAbsent(c, new TrieNode());
            node = node.children.get(c);
        }
        node.isEndOfWord = true;
    }

    // Search - O(m)
    public boolean search(String word) {
        TrieNode node = findNode(word);
        return node != null && node.isEndOfWord;
    }

    // Prefix search - O(m)
    public boolean startsWith(String prefix) {
        return findNode(prefix) != null;
    }

    private TrieNode findNode(String str) {
        TrieNode node = root;
        for (char c : str.toCharArray()) {
            if (!node.children.containsKey(c)) return null;
            node = node.children.get(c);
        }
        return node;
    }

    // Get all words with prefix
    public List<String> getWordsWithPrefix(String prefix) {
        List<String> result = new ArrayList<>();
        TrieNode node = findNode(prefix);
        if (node != null) {
            collectWords(node, new StringBuilder(prefix), result);
        }
        return result;
    }

    private void collectWords(TrieNode node, StringBuilder current, List<String> result) {
        if (node.isEndOfWord) {
            result.add(current.toString());
        }
        for (Map.Entry<Character, TrieNode> entry : node.children.entrySet()) {
            current.append(entry.getKey());
            collectWords(entry.getValue(), current, result);
            current.deleteCharAt(current.length() - 1);
        }
    }
}

Common Tree Problems

Validate BST

public boolean isValidBST(TreeNode root) {
    return isValidBST(root, null, null);
}

private boolean isValidBST(TreeNode node, Integer min, Integer max) {
    if (node == null) return true;
    if (min != null && node.val <= min) return false;
    if (max != null && node.val >= max) return false;
    return isValidBST(node.left, min, node.val) &&
           isValidBST(node.right, node.val, max);
}

Lowest Common Ancestor

// For BST
public TreeNode lowestCommonAncestorBST(TreeNode root, TreeNode p, TreeNode q) {
    while (root != null) {
        if (p.val < root.val && q.val < root.val) {
            root = root.left;
        } else if (p.val > root.val && q.val > root.val) {
            root = root.right;
        } else {
            return root;
        }
    }
    return null;
}

// For Binary Tree
public TreeNode lowestCommonAncestor(TreeNode root, TreeNode p, TreeNode q) {
    if (root == null || root == p || root == q) return root;

    TreeNode left = lowestCommonAncestor(root.left, p, q);
    TreeNode right = lowestCommonAncestor(root.right, p, q);

    if (left != null && right != null) return root;
    return left != null ? left : right;
}

Maximum Depth / Height

public int maxDepth(TreeNode root) {
    if (root == null) return 0;
    return 1 + Math.max(maxDepth(root.left), maxDepth(root.right));
}

Check if Balanced

public boolean isBalanced(TreeNode root) {
    return checkHeight(root) != -1;
}

private int checkHeight(TreeNode node) {
    if (node == null) return 0;

    int left = checkHeight(node.left);
    if (left == -1) return -1;

    int right = checkHeight(node.right);
    if (right == -1) return -1;

    if (Math.abs(left - right) > 1) return -1;
    return 1 + Math.max(left, right);
}

Serialize/Deserialize BST

public String serialize(TreeNode root) {
    StringBuilder sb = new StringBuilder();
    serializeHelper(root, sb);
    return sb.toString();
}

private void serializeHelper(TreeNode node, StringBuilder sb) {
    if (node == null) {
        sb.append("null,");
        return;
    }
    sb.append(node.val).append(",");
    serializeHelper(node.left, sb);
    serializeHelper(node.right, sb);
}

public TreeNode deserialize(String data) {
    Queue<String> queue = new LinkedList<>(Arrays.asList(data.split(",")));
    return deserializeHelper(queue);
}

private TreeNode deserializeHelper(Queue<String> queue) {
    String val = queue.poll();
    if (val.equals("null")) return null;

    TreeNode node = new TreeNode(Integer.parseInt(val));
    node.left = deserializeHelper(queue);
    node.right = deserializeHelper(queue);
    return node;
}

Top K Frequent Elements (Heap)

public int[] topKFrequent(int[] nums, int k) {
    Map<Integer, Integer> count = new HashMap<>();
    for (int num : nums) {
        count.merge(num, 1, Integer::sum);
    }

    PriorityQueue<Integer> heap = new PriorityQueue<>(
        (a, b) -> count.get(a) - count.get(b)
    );

    for (int num : count.keySet()) {
        heap.offer(num);
        if (heap.size() > k) heap.poll();
    }

    return heap.stream().mapToInt(i -> i).toArray();
}

Pros and Cons

Trees Pros and Cons


Java Collections

// TreeMap - Red-Black Tree (balanced BST)
TreeMap<Integer, String> treeMap = new TreeMap<>();
treeMap.put(5, "five");
treeMap.firstKey();           // Smallest key
treeMap.lastKey();            // Largest key
treeMap.floorKey(4);          // Largest key ≤ 4
treeMap.ceilingKey(4);        // Smallest key ≥ 4
treeMap.lowerKey(4);          // Largest key < 4
treeMap.higherKey(4);         // Smallest key > 4
treeMap.subMap(2, 8);         // Keys in range [2, 8)

// TreeSet - Red-Black Tree Set
TreeSet<Integer> treeSet = new TreeSet<>();
treeSet.add(5);
treeSet.first();              // Smallest
treeSet.last();               // Largest
treeSet.floor(4);             // Largest ≤ 4
treeSet.ceiling(4);           // Smallest ≥ 4

// PriorityQueue - Binary Heap
PriorityQueue<Integer> pq = new PriorityQueue<>();