When an interview asks you to answer something about a sub‑array repeatedly—think "sum of elements between i and j", "minimum value in a window", or "how many numbers are greater than X in a range"—the naïve O(n) scan becomes a bottleneck. The segment tree pattern gives you O(log n) per operation after an O(n) preprocessing step, making it the go‑to tool for dynamic range queries.

Why a Segment Tree?

A segment tree is a binary tree where each node stores information about a contiguous segment of the original array. The root covers the whole array, its children cover the left and right halves, and leaves represent single elements. By storing aggregates (sum, min, max, frequency map, etc.) at each node, you can answer a query by combining at most two nodes per level—hence the logarithmic cost.

When to Reach for a Segment Tree

Signal in the statementTypical operationReason to use
"range sum/min/max"Query on any intervalO(log n) vs O(n) scan
"update a single element"Point update that must reflect in future queriesTree can adjust in O(log n)
"multiple queries after each update"Mix of queries and updatesRebuilding a prefix array each time would be too slow
"offline queries are not allowed"Need online answersSegment tree works online
"non‑commutative combine" (e.g., concatenation)Custom merge functionTree can store any associative operation

If the problem mentions static data only (no updates) and the query count is low, a prefix‑sum array might be enough. Conversely, if you need to support both range queries and point updates many times, the segment tree is usually the right choice.

A Minimal Working Example in Python

Below is a compact, readable implementation that supports range sum queries and point updates. You can replace the combine function to handle min, max, or any other associative operation.

class SegmentTree:
    def __init__(self, data):
        self.n = len(data)
        # Next power of two for easy indexing
        size = 1
        while size < self.n:
            size <<= 1
        self.size = size
        self.tree = [0] * (2 * size)
        # Build leaves
        for i, val in enumerate(data):
            self.tree[size + i] = val
        # Build internal nodes
        for i in range(size - 1, 0, -1):
            self.tree[i] = self.tree[2 * i] + self.tree[2 * i + 1]

    def _combine(self, left, right):
        return left + right  # change to min/max/etc.

    def update(self, idx, value):
        # set data[idx] = value
        pos = self.size + idx
        self.tree[pos] = value
        while pos > 1:
            pos //= 2
            self.tree[pos] = self._combine(self.tree[2 * pos], self.tree[2 * pos + 1])

    def query(self, l, r):
        """Return combine over interval [l, r) (half‑open)."""
        l += self.size
        r += self.size
        res_left, res_right = 0, 0  # identity for sum
        while l < r:
            if l & 1:
                res_left = self._combine(res_left, self.tree[l])
                l += 1
            if r & 1:
                r -= 1
                res_right = self._combine(self.tree[r], res_right)
            l //= 2
            r //= 2
        return self._combine(res_left, res_right)


arr = [3, 8, 7, 6, 2, 5]
st = SegmentTree(arr)
print(st.query(1, 5))   # sum of arr[1:5] => 8+7+6+2 = 23
st.update(3, 10)        # change 6 -> 10
print(st.query(1, 5))   # now 8+7+10+2 = 27

Complexity

  • Build: O(n) (the loop over internal nodes is linear).
  • Point update: O(log n) because you walk up the tree.
  • Range query: O(log n) – at most two nodes per level are visited.

Common Pitfalls to Avoid

  1. Off‑by‑one errors – Decide early whether your query method uses inclusive [l, r] or half‑open [l, r). The code above uses half‑open, which aligns with Python slicing.
  2. Identity element – For sum it is 0; for min it should be +inf; for max, -inf. Forgetting this leads to wrong results when a query touches an empty segment.
  3. Tree size – If you allocate exactly 2*n for a non‑power‑of‑two length, the parent‑child relationships break. Padding to the next power of two (as shown) sidesteps the issue.
  4. Mutable combine – When the combine operation is not pure (e.g., concatenating strings), you must be careful about copying to avoid unintended sharing.
  5. Recursive vs iterative – Recursive implementations are easier to read but can hit recursion limits for very large inputs. The iterative version above avoids that risk.

Five Representative Practice Problems

Below are five problems you can find on typical coding platforms. They are described abstractly to avoid copying exact statements. Use the hints to shape your solution.

  1. Range Sum with Point Updates – Given an array of up to 10⁵ integers, support two operations: update(i, x) (set a[i]=x) and sum(l, r) (inclusive). Hint: Classic segment tree with sum combine.
  2. Maximum Subarray Sum in a Range – For each query [l, r], return the maximum possible sum of a contiguous subarray inside that interval. Hint: Store four values per node – total sum, best prefix, best suffix, and best subarray. Merge using Kadane‑style logic.
  3. Frequency of a Value in a Range – The array contains numbers from 1‑100. Queries ask for how many times v appears between l and r. Hint: Keep a frequency vector of size 100 at each node; merging is element‑wise addition.
  4. Range Minimum Query with Lazy Propagation – The array starts at zero. Two operations: add a constant c to every element in [l, r] and query the minimum in a range. Hint: Use a lazy‑propagation segment tree; store the current minimum and a pending addition.
  5. Longest Increasing Subsequence Length in a Range – For each query [l, r], report the length of the longest increasing subsequence confined to that interval. Hint: This is tougher; store a small “profile” at each node (e.g., the first few elements and their LIS lengths) and combine carefully. It illustrates how segment trees can be extended with custom state.

How to Practice This

  1. Build the skeleton – Write a generic SegmentTree class that takes a combine lambda and an identity value. Run a sanity test with random updates and queries, comparing against a brute‑force loop.
  2. Solve one problem at a time – Pick the first problem, implement the specific combine logic, and verify against the platform’s test suite. Focus on getting the edge cases (empty range, single element) right before optimizing.
  3. Explain your solution aloud – Use Call Assistant to rehearse a concise answer: describe the data structure, its complexity, and why it fits the problem. Speaking the explanation helps solidify the pattern for the real interview.

FAQ

  • When is a Fenwick tree preferable to a segment tree? Fenwick (Binary Indexed) trees are simpler and use less memory, but they only support prefix‑type queries (e.g., sum) and point updates. If you need arbitrary range queries (like min or custom aggregates), a segment tree is more flexible.
  • Do I always need lazy propagation? Only when you have range updates (e.g., add a value to every element in [l, r]). Point updates can be handled without laziness.
  • Can I store non‑numeric data in a segment tree? Yes. As long as the merge operation is associative and you have a clear identity element, you can store strings, sets, or custom objects.
  • What’s the memory overhead? An iterative tree uses roughly 2 * 2^⌈log₂ n⌉ slots. For n up to 10⁵, this is under a megabyte of integers, which is negligible for interview constraints.

Frequently asked questions

When is a Fenwick tree preferable to a segment tree?

Fenwick trees are simpler and use less memory, but they only support prefix‑type queries and point updates. Use them when you only need sums (or other invertible ops) on prefixes; choose a segment tree for arbitrary range queries or custom aggregates.

Do I always need lazy propagation?

Lazy propagation is required only when the problem includes range updates (e.g., adding a constant to every element in a subarray). For pure point updates, a standard segment tree suffices.

Can I store non‑numeric data in a segment tree?

Yes. As long as the merge function is associative and you have an appropriate identity element, you can store strings, sets, or custom objects in the nodes.

What’s the memory overhead of a segment tree?

An iterative implementation uses roughly twice the next power‑of‑two size of the input array. For n ≈ 10⁵ this is under a megabyte of integers, which is negligible for interview constraints.

#coding pattern#segment trees#interview prep#data structures#python