Zero To DSAZero To DSA
Privacy Policy·FAQ·Report a Bug·Support on Ko‑fi
Learning Path
Introduction to DSABig O & ComplexityArrays & StringsHashMaps & SetsLinked ListsStacks & QueuesRecursion & BacktrackingSearching & SortingTrees & TriesGreedy & IntervalsGraphsDynamic Programming

Trees & Tries

intermediate5 problems
Prerequisites:Big O & ComplexityArrays & StringsStacks & QueuesRecursion & Backtracking
Learn
Practice
Exam

When Data Has a Hierarchy

So far, our data structures have been either linear (arrays, linked lists, stacks, queues - one element after another) or hash-based (hash maps and sets - instant lookup by value). Both arrange data on a flat plane.

But real-world data is often hierarchical. Your file system has folders inside folders. A company has managers who manage managers who manage employees.

A tree is the data structure for this. It's a collection of nodes, where each node has a value and children - forming a branching structure.

Trees introduce two big ideas you'll carry through the rest of the course:

  1. Recursive structure - every node in a tree is itself the root of a smaller tree
  2. Traversal order - the order you visit nodes changes what you can compute

Tree Basics

A tree has a root node at the top. Each node has a value and children. Nodes with no children are called leaves.

Two common tree types:

  • Binary Tree - each node has at most 2 children (left and right)
  • Binary Search Tree (BST) - left child < parent < right child. This property makes searching O(log n) in a balanced tree.
OperationBST (balanced)BST (skewed)
SearchO(log n)O(n)
InsertO(log n)O(n)
DeleteO(log n)O(n)
TraverseO(n)O(n)

Problem

Visit every node of this binary search tree in a specific traversal order.

Tree TraversalStep 1 / 8
2010515302535
Visiting 5
5
Speed:
Unvisited Current VisitedInorder: left → root → right
TreeNode class and traversalsC#
public class TreeNode {
    public int val;
    public TreeNode left;
    public TreeNode right;
    public TreeNode(int val = 0, TreeNode left = null, TreeNode right = null) {
        this.val = val;
        this.left = left;
        this.right = right;
    }
}

// DFS Traversals (recursive)
void Inorder(TreeNode node) {      // left -> root -> right
    if (node == null) return;
    Inorder(node.left);
    Console.Write(node.val + " ");
    Inorder(node.right);
}

void Preorder(TreeNode node) {     // root -> left -> right
    if (node == null) return;
    Console.Write(node.val + " ");
    Preorder(node.left);
    Preorder(node.right);
}

void Postorder(TreeNode node) {    // left -> right -> root
    if (node == null) return;
    Postorder(node.left);
    Postorder(node.right);
    Console.Write(node.val + " ");
}

// BFS (Level order)
void LevelOrder(TreeNode root) {
    var q = new Queue<TreeNode>();
    q.Enqueue(root);
    while (q.Count > 0) {
        var node = q.Dequeue();
        Console.Write(node.val + " ");
        if (node.left != null) q.Enqueue(node.left);
        if (node.right != null) q.Enqueue(node.right);
    }
}

BST Operations

BST property: left.val < node.val < right.val for ALL nodes (not just immediate children).

This property enables fast search - in a balanced tree, at each node you eliminate roughly half the remaining nodes.

BST search, insert, validateC#
// BST Search - O(log n) average
TreeNode Search(TreeNode root, int target) {
    while (root != null) {
        if (root.val == target) return root;
        root = target < root.val ? root.left : root.right;
    }
    return null;
}

// BST Insert - O(log n)
TreeNode Insert(TreeNode root, int val) {
    if (root == null) return new TreeNode(val);
    if (val < root.val) root.left = Insert(root.left, val);
    else root.right = Insert(root.right, val);
    return root;
}

// Validate BST - O(n)
bool IsValidBST(TreeNode root) {
    return Validate(root, long.MinValue, long.MaxValue);
}

bool Validate(TreeNode node, long min, long max) {
    if (node == null) return true;
    if (node.val <= min || node.val >= max) return false;
    return Validate(node.left, min, node.val)
        && Validate(node.right, node.val, max);
}

When to Use Trees

Trees are the right choice when:

  • Data has a hierarchical structure (filesystem, DOM, org chart)
  • You need fast search with ordering (BST: O(log n) average)
  • You need range queries (BST: find all values between x and y)
  • Data naturally branches (decision trees, game trees)

Trees vs other structures:

StructureUse when
Array / ListFlat, ordered data with index access
Hash mapKey-value lookup (no ordering needed)
TreeHierarchical OR needs sorted order OR range queries
Graph (Module 10)No hierarchy, arbitrary connections

When NOT to use a tree:

  • Data is flat (just use an array)
  • You only need exact membership lookup (use a hash set)
  • The tree would be unbalanced (use a balanced tree library)

Decision guide:

SignalStructure
"Sorted order of keys"BST
"Find all values in range"BST
"Hierarchy / parent-child"Tree
"Fast lookup, no order needed"Hash set/map
"Relationships between items"Graph (Module 10)

Common Mistakes

BST validation: checking only immediate children: A common mistake: left.val < node.val < right.val. This is WRONG. ALL nodes in the left subtree must be < node.val. Example: root=5, left=3, left.right=6 would pass the naive check but violates BST (6 > 5). Use the min/max range approach.

Forgetting null checks on tree nodes: node.left and node.right can be null. Always check if (node == null) return before accessing children.

Recursion depth in skewed trees: A tree with n nodes can be n levels deep if it's skewed (basically a linked list). Recursive traversal uses O(n) stack space and risks overflow. Consider iterative traversal for worst-case trees.

Trie (Prefix Tree)

A trie (prefix tree) stores strings in a tree where each node represents a character. This is a more advanced topic. Make sure you are comfortable with trees and hash maps first.

Used for autocomplete, spell check, and IP routing.

OperationTrie
InsertO(m) where m = word length
SearchO(m)
Prefix searchO(m)

Space: O(total characters across all words).

Trie implementationC#
public class TrieNode {
    public Dictionary<char, TrieNode> children = new();
    public bool isEnd = false;
}

public class Trie {
    private readonly TrieNode root = new();

    public void Insert(string word) {
        var node = root;
        foreach (char c in word) {
            if (!node.children.ContainsKey(c))
                node.children[c] = new TrieNode();
            node = node.children[c];
        }
        node.isEnd = true;
    }

    public bool Search(string word) {
        var node = Find(word);
        return node != null && node.isEnd;
    }

    public bool StartsWith(string prefix) {
        return Find(prefix) != null;
    }

    private TrieNode Find(string s) {
        var node = root;
        foreach (char c in s) {
            if (!node.children.TryGetValue(c, out node))
                return null;
        }
        return node;
    }
}

Key Patterns to Remember

  1. Tree traversal - inorder (sorted BST), preorder (serialization), level order (BFS)
  2. LCA (Lowest Common Ancestor) - recursive or iterative for BST
  3. Diameter / max path sum - post-order traversal with global variable
  4. Serialize / deserialize - preorder with null markers
  5. Balanced tree check - compute height and check balance at each node

What's Next?

Trees introduced you to hierarchical data and recursive traversal. A tree has a strict parent-child relationship - each node (except the root) has exactly one parent.

So far, every algorithm you've learned explores all possibilities or follows a fixed rule. But what if you could solve a problem by simply making the best choice at each step? That's the idea behind Greedy Algorithms - and it works for a surprising number of problems, from interval scheduling to stock trading.

If trees are about navigating hierarchy, greedy algorithms are about trusting your instincts.

Next up: Greedy & Intervals