Home DSA Segment Trees Explained — Build, Query, Update and Lazy Propagation

Segment Trees Explained — Build, Query, Update and Lazy Propagation

In Plain English 🔥
Imagine a sports league that tracks the top scorer across 16 teams. Instead of checking all 16 teams every time someone asks 'who scored most between teams 3 and 11?', you pre-group teams into pairs, then groups of four, then groups of eight — so you can answer any range question by checking just 4 pre-computed buckets instead of 9 individual teams. A Segment Tree is exactly that pre-grouping structure, but for any array and any aggregation you care about — sum, min, max, GCD, you name it.
⚡ Quick Answer
Imagine a sports league that tracks the top scorer across 16 teams. Instead of checking all 16 teams every time someone asks 'who scored most between teams 3 and 11?', you pre-group teams into pairs, then groups of four, then groups of eight — so you can answer any range question by checking just 4 pre-computed buckets instead of 9 individual teams. A Segment Tree is exactly that pre-grouping structure, but for any array and any aggregation you care about — sum, min, max, GCD, you name it.

Range queries are everywhere. Stock price analytics, game leaderboards, genomic data analysis, database index scans — all of them need to answer questions like 'what is the maximum value between index 3 and index 47?' millions of times per second. If your data never changed, a prefix-sum array would suffice. But real data does change, and that's where the naive approaches fall apart embarrassingly fast.

The brute-force fix — loop over the range every time — costs O(n) per query. A prefix-sum array costs O(1) per query but O(n) per update because you have to recompute everything downstream. Neither is acceptable when you're handling millions of operations on an array of 100,000 elements. You need a data structure that keeps both query time and update time logarithmic, simultaneously. That's the exact gap Segment Trees were designed to close.

By the end of this article you'll be able to build a Segment Tree from scratch, handle point updates, execute range queries, and — the part that trips up most developers — implement lazy propagation for range updates. You'll also understand the memory layout internals, know exactly when a Segment Tree is and isn't the right tool, and walk into any interview prepared to discuss the tradeoffs confidently.

How a Segment Tree Is Structured — Internals That Actually Matter

A Segment Tree is a full binary tree where every node stores the aggregated result (say, the sum) of a contiguous sub-array. The root covers the entire array. Its left child covers the left half, its right child the right half, and so on recursively until each leaf covers exactly one element.

The magic is in the storage trick: you don't use an explicit linked tree. You store the entire tree in a flat array of size 4n, where n is the length of the input array. For a node at index i, its left child lives at 2i and its right child at 2*i+1. This means zero pointer overhead and cache-friendly sequential memory access during traversal — both critical for performance.

Why 4n and not 2n? Because the tree height is ceil(log2(n)), and the last level can have up to 2^ceil(log2(n)) nodes. For n=5 that's already 8 leaf slots but 16 total slots needed in the worst case. Using 4n is a safe upper bound that avoids out-of-bounds writes without requiring exact size calculation. In production you'd compute the exact size as 2 nextPowerOf2(n), but 4*n is universally safe for interviews and most implementations.

The build phase populates this array bottom-up in O(n) time. Every internal node's value is computed from its two children — not from the original array. This is what enables O(log n) updates later: changing one leaf ripples upward along at most log(n) ancestors.

SegmentTreeBuildAndQuery.java · JAVA
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
public class SegmentTreeBuildAndQuery {

    // The flat array that stores all segment tree nodes.
    // Size 4*n is a safe upper bound for any input array of length n.
    private final int[] tree;
    private final int   inputLength;

    public SegmentTreeBuildAndQuery(int[] inputArray) {
        this.inputLength = inputArray.length;
        // Allocate 4x the input size to safely hold all tree nodes
        this.tree = new int[4 * inputLength];
        // Kick off recursive build from root node (index 1) covering full range
        buildTree(inputArray, 1, 0, inputLength - 1);
    }

    /**
     * Recursively builds the segment tree.
     *
     * @param inputArray  The original data array
     * @param nodeIndex   Current position in the flat tree array (1-indexed)
     * @param rangeStart  Left boundary of the range this node is responsible for
     * @param rangeEnd    Right boundary of the range this node is responsible for
     */
    private void buildTree(int[] inputArray, int nodeIndex, int rangeStart, int rangeEnd) {
        if (rangeStart == rangeEnd) {
            // Leaf node: directly store the input value
            tree[nodeIndex] = inputArray[rangeStart];
            return;
        }

        int mid = rangeStart + (rangeEnd - rangeStart) / 2; // avoids integer overflow vs (start+end)/2

        // Recursively build left child (covers rangeStart..mid)
        buildTree(inputArray, 2 * nodeIndex, rangeStart, mid);
        // Recursively build right child (covers mid+1..rangeEnd)
        buildTree(inputArray, 2 * nodeIndex + 1, mid + 1, rangeEnd);

        // Internal node stores SUM of its two children
        tree[nodeIndex] = tree[2 * nodeIndex] + tree[2 * nodeIndex + 1];
    }

    /**
     * Returns the sum of all elements in inputArray[queryLeft..queryRight].
     * Time complexity: O(log n)
     */
    public int rangeSum(int queryLeft, int queryRight) {
        return queryHelper(1, 0, inputLength - 1, queryLeft, queryRight);
    }

    private int queryHelper(int nodeIndex, int rangeStart, int rangeEnd,
                             int queryLeft, int queryRight) {

        // Case 1: This node's range is completely OUTSIDE the query range — contribute nothing
        if (queryRight < rangeStart || rangeEnd < queryLeft) {
            return 0; // identity element for sum (use Integer.MAX_VALUE for min queries)
        }

        // Case 2: This node's range is completely INSIDE the query range — return full value
        if (queryLeft <= rangeStart && rangeEnd <= queryRight) {
            return tree[nodeIndex];
        }

        // Case 3: Partial overlap — split and recurse into both children
        int mid = rangeStart + (rangeEnd - rangeStart) / 2;
        int leftSum  = queryHelper(2 * nodeIndex,     rangeStart, mid,      queryLeft, queryRight);
        int rightSum = queryHelper(2 * nodeIndex + 1, mid + 1,    rangeEnd, queryLeft, queryRight);

        return leftSum + rightSum;
    }

    /**
     * Updates a single element at position targetIndex to newValue.
     * Time complexity: O(log n)
     */
    public void pointUpdate(int targetIndex, int newValue) {
        updateHelper(1, 0, inputLength - 1, targetIndex, newValue);
    }

    private void updateHelper(int nodeIndex, int rangeStart, int rangeEnd,
                               int targetIndex, int newValue) {

        if (rangeStart == rangeEnd) {
            // Reached the leaf for our target — update it directly
            tree[nodeIndex] = newValue;
            return;
        }

        int mid = rangeStart + (rangeEnd - rangeStart) / 2;

        if (targetIndex <= mid) {
            // Target is in the LEFT half
            updateHelper(2 * nodeIndex, rangeStart, mid, targetIndex, newValue);
        } else {
            // Target is in the RIGHT half
            updateHelper(2 * nodeIndex + 1, mid + 1, rangeEnd, targetIndex, newValue);
        }

        // On the way back UP, recompute this node from its (now updated) children
        tree[nodeIndex] = tree[2 * nodeIndex] + tree[2 * nodeIndex + 1];
    }

    public static void main(String[] args) {
        int[] scores = {3, 7, 1, 9, 4, 6, 2, 8};
        // Represents 8 players' scores: [3, 7, 1, 9, 4, 6, 2, 8]

        SegmentTreeBuildAndQuery segTree = new SegmentTreeBuildAndQuery(scores);

        System.out.println("=== Segment Tree: Sum Queries ===");
        System.out.println("Sum of scores[0..7] (all):   " + segTree.rangeSum(0, 7)); // 40
        System.out.println("Sum of scores[1..4]:         " + segTree.rangeSum(1, 4)); // 21
        System.out.println("Sum of scores[3..3] (single):" + segTree.rangeSum(3, 3)); // 9

        // Player at index 2 improves their score from 1 to 10
        segTree.pointUpdate(2, 10);
        System.out.println("\nAfter updating scores[2] from 1 to 10:");
        System.out.println("Sum of scores[0..7] (all):   " + segTree.rangeSum(0, 7)); // 49
        System.out.println("Sum of scores[1..4]:         " + segTree.rangeSum(1, 4)); // 30
    }
}
▶ Output
=== Segment Tree: Sum Queries ===
Sum of scores[0..7] (all): 40
Sum of scores[1..4]: 21
Sum of scores[3..3] (single):9

After updating scores[2] from 1 to 10:
Sum of scores[0..7] (all): 49
Sum of scores[1..4]: 30
⚠️
Watch Out: Integer Overflow in Mid CalculationNever compute mid as (rangeStart + rangeEnd) / 2. If both are large positive ints (e.g., near Integer.MAX_VALUE / 2), their sum overflows to a negative number. Always use rangeStart + (rangeEnd - rangeStart) / 2. This is a real bug that bites even experienced developers.

Lazy Propagation — How to Handle Range Updates in O(log n)

Point updates are straightforward, but what happens when you need to add 5 to every element in scores[2..6]? Without lazy propagation, you'd call pointUpdate five times — O(n log n) in the worst case for a full-range update. That kills the performance advantage entirely.

Lazy propagation solves this by deferring work. Instead of immediately pushing an update down to all affected leaves, you store a 'pending update' (the lazy value) at the highest node whose range is completely covered by your update range. The promise is: 'I know my children need this update, but I'll tell them only when someone actually needs to query or update them.'

This requires a second array of the same size as the tree — the lazy array — initialized to 0. When you hit a node whose range is fully inside the update range, you apply the update to that node and record the pending delta in lazy[nodeIndex]. When you later revisit any node that has a non-zero lazy value, you first push it down to both children before doing anything else. This 'push down before you proceed' discipline is the entire mechanism.

The correctness invariant to keep in mind: a node's tree[nodeIndex] is always accurate and up to date for its range, including any lazy values already applied to it. The lazy value only represents work that hasn't yet been pushed to the children — not work that hasn't been applied to the node itself.

SegmentTreeLazyPropagation.java · JAVA
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
public class SegmentTreeLazyPropagation {

    private final int[] tree;  // Stores actual aggregated sums
    private final int[] lazy;  // Stores pending add-deltas not yet pushed to children
    private final int   inputLength;

    public SegmentTreeLazyPropagation(int[] inputArray) {
        this.inputLength = inputArray.length;
        this.tree = new int[4 * inputLength];
        this.lazy = new int[4 * inputLength]; // all zeros by default — no pending updates
        buildTree(inputArray, 1, 0, inputLength - 1);
    }

    private void buildTree(int[] inputArray, int nodeIndex, int rangeStart, int rangeEnd) {
        if (rangeStart == rangeEnd) {
            tree[nodeIndex] = inputArray[rangeStart];
            return;
        }
        int mid = rangeStart + (rangeEnd - rangeStart) / 2;
        buildTree(inputArray, 2 * nodeIndex,     rangeStart, mid);
        buildTree(inputArray, 2 * nodeIndex + 1, mid + 1,    rangeEnd);
        tree[nodeIndex] = tree[2 * nodeIndex] + tree[2 * nodeIndex + 1];
    }

    /**
     * Pushes a node's pending lazy value down to its two children.
     * Call this BEFORE descending into children during any query or update.
     *
     * @param nodeIndex  The node that currently holds a pending lazy value
     * @param rangeStart Start of this node's responsible range
     * @param rangeEnd   End of this node's responsible range
     */
    private void pushDown(int nodeIndex, int rangeStart, int rangeEnd) {
        if (lazy[nodeIndex] == 0) return; // Nothing to push — fast exit

        int mid          = rangeStart + (rangeEnd - rangeStart) / 2;
        int leftChild    = 2 * nodeIndex;
        int rightChild   = 2 * nodeIndex + 1;
        int leftCount    = mid - rangeStart + 1; // number of elements in left child's range
        int rightCount   = rangeEnd - mid;       // number of elements in right child's range

        // Apply the pending delta to left child's sum
        tree[leftChild]  += lazy[nodeIndex] * leftCount;
        lazy[leftChild]  += lazy[nodeIndex]; // propagate pending delta further if needed later

        // Apply the pending delta to right child's sum
        tree[rightChild] += lazy[nodeIndex] * rightCount;
        lazy[rightChild] += lazy[nodeIndex];

        // This node's lazy debt is now paid — clear it
        lazy[nodeIndex] = 0;
    }

    /**
     * Adds 'delta' to every element in inputArray[updateLeft..updateRight].
     * Time complexity: O(log n)
     */
    public void rangeAdd(int updateLeft, int updateRight, int delta) {
        rangeAddHelper(1, 0, inputLength - 1, updateLeft, updateRight, delta);
    }

    private void rangeAddHelper(int nodeIndex, int rangeStart, int rangeEnd,
                                 int updateLeft, int updateRight, int delta) {

        // Completely outside — nothing to do
        if (updateRight < rangeStart || rangeEnd < updateLeft) return;

        // Completely inside — apply delta to this whole node and mark lazy
        if (updateLeft <= rangeStart && rangeEnd <= updateRight) {
            int rangeLength = rangeEnd - rangeStart + 1;
            tree[nodeIndex] += delta * rangeLength; // sum increases by delta * count of elements
            lazy[nodeIndex] += delta;               // record the per-element pending delta
            return;
        }

        // Partial overlap — push pending work to children FIRST, then recurse
        pushDown(nodeIndex, rangeStart, rangeEnd);

        int mid = rangeStart + (rangeEnd - rangeStart) / 2;
        rangeAddHelper(2 * nodeIndex,     rangeStart, mid,      updateLeft, updateRight, delta);
        rangeAddHelper(2 * nodeIndex + 1, mid + 1,    rangeEnd, updateLeft, updateRight, delta);

        // Recompute this node from children after children were updated
        tree[nodeIndex] = tree[2 * nodeIndex] + tree[2 * nodeIndex + 1];
    }

    /**
     * Returns the sum of inputArray[queryLeft..queryRight], respecting all pending lazy updates.
     * Time complexity: O(log n)
     */
    public int rangeSum(int queryLeft, int queryRight) {
        return querySumHelper(1, 0, inputLength - 1, queryLeft, queryRight);
    }

    private int querySumHelper(int nodeIndex, int rangeStart, int rangeEnd,
                                int queryLeft, int queryRight) {

        if (queryRight < rangeStart || rangeEnd < queryLeft) return 0;

        if (queryLeft <= rangeStart && rangeEnd <= queryRight) {
            return tree[nodeIndex]; // already accurate — lazy was applied when range update hit
        }

        // Partial overlap — push pending work to children BEFORE reading them
        pushDown(nodeIndex, rangeStart, rangeEnd);

        int mid = rangeStart + (rangeEnd - rangeStart) / 2;
        int leftSum  = querySumHelper(2 * nodeIndex,     rangeStart, mid,      queryLeft, queryRight);
        int rightSum = querySumHelper(2 * nodeIndex + 1, mid + 1,    rangeEnd, queryLeft, queryRight);

        return leftSum + rightSum;
    }

    public static void main(String[] args) {
        // Daily temperatures recorded for 6 days
        int[] temperatures = {10, 20, 30, 40, 50, 60};

        SegmentTreeLazyPropagation segTree = new SegmentTreeLazyPropagation(temperatures);

        System.out.println("=== Lazy Propagation: Range Add + Range Sum ===");
        System.out.println("Initial sum of temps[0..5]: " + segTree.rangeSum(0, 5)); // 210
        System.out.println("Initial sum of temps[1..3]: " + segTree.rangeSum(1, 3)); // 90

        // A calibration offset: add 5 to all readings from day 1 to day 4 (index 1..4)
        segTree.rangeAdd(1, 4, 5);
        System.out.println("\nAfter adding 5 to temps[1..4]:");
        System.out.println("Sum of temps[0..5]: " + segTree.rangeSum(0, 5)); // 210 + 5*4 = 230
        System.out.println("Sum of temps[1..3]: " + segTree.rangeSum(1, 3)); // 90  + 5*3 = 105
        System.out.println("Sum of temps[0..0]: " + segTree.rangeSum(0, 0)); // 10 (untouched)
        System.out.println("Sum of temps[5..5]: " + segTree.rangeSum(5, 5)); // 60 (untouched)

        // Another range update overlapping partially
        segTree.rangeAdd(3, 5, 10);
        System.out.println("\nAfter adding 10 to temps[3..5]:");
        System.out.println("Sum of temps[0..5]: " + segTree.rangeSum(0, 5)); // 230 + 10*3 = 260
        System.out.println("Sum of temps[3..5]: " + segTree.rangeSum(3, 5)); // (45+55+65) = 165
    }
}
▶ Output
=== Lazy Propagation: Range Add + Range Sum ===
Initial sum of temps[0..5]: 210
Initial sum of temps[1..3]: 90

After adding 5 to temps[1..4]:
Sum of temps[0..5]: 230
Sum of temps[1..3]: 105
Sum of temps[0..0]: 10
Sum of temps[5..5]: 60

After adding 10 to temps[3..5]:
Sum of temps[0..5]: 260
Sum of temps[3..5]: 165
⚠️
Pro Tip: Always Push Down Before DescendingThe golden rule of lazy propagation is: call pushDown(nodeIndex, ...) at the top of any partial-overlap branch, before you recurse into children. Forgetting this in even one place produces silent, hard-to-debug wrong answers because tree[childNode] will reflect a stale sum. Write pushDown as its own method and make it a reflex to call it wherever you see partial overlap.

Segment Tree Variants and When to Use Each One

The sum Segment Tree you've seen is just one flavor. The architecture generalizes to any associative operation — meaning any operation where (a ⊕ b) ⊕ c = a ⊕ (b ⊕ c). Sum, min, max, GCD, LCM, bitwise AND/OR, matrix multiplication — all associative, all valid.

For a range-minimum query (RMQ) Segment Tree, swap the merge operation from '+' to 'Math.min()', and change the identity element from 0 to Integer.MAX_VALUE. That's literally it — the entire tree structure and traversal logic remain identical. The same applies to max (identity: Integer.MIN_VALUE) and GCD (identity: 0).

Lazy propagation also adapts but requires more care. For range-add with sum queries, the lazy value is simply an additive delta. For range-set (set all elements in a range to a given value) with min queries, the lazy value replaces the min entirely. Combining multiple operation types (e.g., range-add AND range-set) requires composable lazy tags — a more advanced technique where you define how two pending operations compose, to ensure correctness when multiple unresolved updates stack.

When should you reach for a Segment Tree over alternatives? Use it when you need both arbitrary range queries AND updates in logarithmic time. If your data is static (no updates), a sparse table gives O(1) queries. If your updates are only prefix-based, a Binary Indexed Tree (Fenwick Tree) is simpler to implement and has lower constant factors. Segment Trees win specifically when you need non-prefix range queries with frequent updates.

SegmentTreeRangeMinimum.java · JAVA
1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798
/**
 * Range Minimum Query (RMQ) Segment Tree.
 * Demonstrates how swapping the merge function transforms the tree's purpose
 * while keeping the entire structural logic identical.
 */
public class SegmentTreeRangeMinimum {

    private final int[] tree;
    private final int   inputLength;

    // Identity element for MIN: anything Math.min(x, MAX_VALUE) == x
    private static final int IDENTITY = Integer.MAX_VALUE;

    public SegmentTreeRangeMinimum(int[] inputArray) {
        this.inputLength = inputArray.length;
        this.tree = new int[4 * inputLength];
        buildTree(inputArray, 1, 0, inputLength - 1);
    }

    private void buildTree(int[] inputArray, int nodeIndex, int rangeStart, int rangeEnd) {
        if (rangeStart == rangeEnd) {
            tree[nodeIndex] = inputArray[rangeStart];
            return;
        }
        int mid = rangeStart + (rangeEnd - rangeStart) / 2;
        buildTree(inputArray, 2 * nodeIndex,     rangeStart, mid);
        buildTree(inputArray, 2 * nodeIndex + 1, mid + 1,    rangeEnd);

        // THE ONLY CHANGE FROM SUM TREE: merge with Math.min instead of '+'
        tree[nodeIndex] = Math.min(tree[2 * nodeIndex], tree[2 * nodeIndex + 1]);
    }

    /**
     * Returns the minimum value in inputArray[queryLeft..queryRight].
     */
    public int rangeMin(int queryLeft, int queryRight) {
        return queryMinHelper(1, 0, inputLength - 1, queryLeft, queryRight);
    }

    private int queryMinHelper(int nodeIndex, int rangeStart, int rangeEnd,
                                int queryLeft, int queryRight) {

        // Out of range: return identity (MAX_VALUE won't affect a min comparison)
        if (queryRight < rangeStart || rangeEnd < queryLeft) return IDENTITY;

        // Full coverage: return stored min
        if (queryLeft <= rangeStart && rangeEnd <= queryRight) return tree[nodeIndex];

        int mid      = rangeStart + (rangeEnd - rangeStart) / 2;
        int leftMin  = queryMinHelper(2 * nodeIndex,     rangeStart, mid,      queryLeft, queryRight);
        int rightMin = queryMinHelper(2 * nodeIndex + 1, mid + 1,    rangeEnd, queryLeft, queryRight);

        // Merge: take the minimum from both halves
        return Math.min(leftMin, rightMin);
    }

    /**
     * Updates a single element and propagates the change upward.
     */
    public void pointUpdate(int targetIndex, int newValue) {
        updateHelper(1, 0, inputLength - 1, targetIndex, newValue);
    }

    private void updateHelper(int nodeIndex, int rangeStart, int rangeEnd,
                               int targetIndex, int newValue) {
        if (rangeStart == rangeEnd) {
            tree[nodeIndex] = newValue;
            return;
        }
        int mid = rangeStart + (rangeEnd - rangeStart) / 2;
        if (targetIndex <= mid)
            updateHelper(2 * nodeIndex,     rangeStart, mid,      targetIndex, newValue);
        else
            updateHelper(2 * nodeIndex + 1, mid + 1,    rangeEnd, targetIndex, newValue);

        // Recompute min from updated children
        tree[nodeIndex] = Math.min(tree[2 * nodeIndex], tree[2 * nodeIndex + 1]);
    }

    public static void main(String[] args) {
        // Sensor readings across 8 time slots
        int[] sensorReadings = {14, 3, 18, 7, 2, 11, 5, 9};

        SegmentTreeRangeMinimum rmqTree = new SegmentTreeRangeMinimum(sensorReadings);

        System.out.println("=== Range Minimum Query Tree ===");
        System.out.println("Min in readings[0..7] (all):  " + rmqTree.rangeMin(0, 7)); // 2
        System.out.println("Min in readings[0..2]:        " + rmqTree.rangeMin(0, 2)); // 3
        System.out.println("Min in readings[3..6]:        " + rmqTree.rangeMin(3, 6)); // 2
        System.out.println("Min in readings[5..7]:        " + rmqTree.rangeMin(5, 7)); // 5

        // Sensor 4 (index 4) malfunctions and reads 100 instead
        rmqTree.pointUpdate(4, 100);
        System.out.println("\nAfter updating readings[4] from 2 to 100:");
        System.out.println("Min in readings[0..7] (all):  " + rmqTree.rangeMin(0, 7)); // 3
        System.out.println("Min in readings[3..6]:        " + rmqTree.rangeMin(3, 6)); // 5
    }
}
▶ Output
=== Range Minimum Query Tree ===
Min in readings[0..7] (all): 2
Min in readings[0..2]: 3
Min in readings[3..6]: 2
Min in readings[5..7]: 5

After updating readings[4] from 2 to 100:
Min in readings[0..7] (all): 3
Min in readings[3..6]: 5
🔥
Interview Gold: Why Segment Trees Over Sparse Tables for RMQ?Sparse Tables answer range-minimum queries in O(1) but require O(n log n) space and cannot handle updates at all — they'd need a full O(n log n) rebuild. If an interviewer asks about RMQ, stating this tradeoff clearly — and knowing Segment Trees handle dynamic data while Sparse Tables are ideal for static data — signals genuine depth beyond textbook knowledge.
Feature / AspectSegment TreeFenwick Tree (BIT)Sparse Table
Range Query TimeO(log n)O(log n)O(1)
Point Update TimeO(log n)O(log n)O(n log n) rebuild
Range Update TimeO(log n) with lazyO(log n) with BIT of BITsNot supported
Supported OperationsAny associative opOnly invertible ops (sum, XOR)Idempotent ops (min, max, GCD)
Memory UsageO(n) — 4n arrayO(n) — n+1 arrayO(n log n)
Implementation ComplexityMedium–HighLow–MediumMedium

🎯 Key Takeaways

    🔥
    TheCodeForge Editorial Team Verified Author

    Written and reviewed by senior developers with real-world experience across enterprise, startup and open-source projects. Every article on TheCodeForge is written to be clear, accurate and genuinely useful — not just SEO filler.

    ← PreviousRed-Black TreeNext →Fenwick Tree — Binary Indexed Tree
    Forged with 🔥 at TheCodeForge.io — Where Developers Are Forged