Skip to main content

Using Segment Tree Patterns

1. Implementing Range Sum Queries

Operation Description Time Complexity Space Complexity
Build Segment Tree Constructs the segment tree from an array of elements O(n) O(n)
Range Sum Query Computes the sum of elements in a given range O(log n) O(1) for query, O(n) for construction
Update Element Updates the value of an element in the array O(log n) O(1) for update, O(n) for construction

Example: Building and Querying a Segment Tree

class SegmentTree {
  constructor(arr) {
    this.n = arr.length;
    this.tree = new Array(4 * this.n).fill(0);
    this.build(arr, 0, 0, this.n - 1);
  }

  build(arr, node, start, end) {
    if (start === end) {
      this.tree[node] = arr[start];
    } else {
      const mid = Math.floor((start + end) / 2);
      this.build(arr, 2 * node + 1, start, mid);
      this.build(arr, 2 * node + 2, mid + 1, end);
      this.tree[node] = this.tree[2 * node + 1] + this.tree[2 * node + 2];
    }
  }

  query(node, start, end, l, r) {
    if (r < start || end < l) return 0;
    if (l <= start && end <= r) return this.tree[node];

    const mid = Math.floor((start + end) / 2);
    return this.query(2 * node + 1, start, mid, l, r) +
           this.query(2 * node + 2, mid + 1, end, l, r);
  }

  sumRange(left, right) {
    return this.query(0, 0, this.n - 1, left, right);
  }
}

// Example usage:
const arr = [1, 3, 5, 7, 9, 11];
const segTree = new SegmentTree(arr);
console.log(segTree.sumRange(1, 3)); // Output: 15 (3 + 5 + 7)
Note: The segment tree is built as a binary heap where each node stores the sum of its children. For queries, we traverse only the relevant segments to compute the range sum efficiently.

2. Implementing Range Minimum Queries

Operation Description Time Complexity Space Complexity
Build Constructs the segment tree from an array of elements O(n) O(n)
Query Find minimum element in a given range [l, r] O(log n) O(1)
Update Update value at a specific index in the array O(log n) O(1)

Example: Building and Querying a Segment Tree

class SegmentTree {
  constructor(arr) {
    this.n = arr.length;
    this.tree = new Array(4 * this.n).fill(0);
    this.build(arr, 0, 0, this.n - 1);
  }

  build(arr, node, start, end) {
    if (start === end) {
      this.tree[node] = arr[start];
    } else {
      const mid = Math.floor((start + end) / 2);
      this.build(arr, 2 * node + 1, start, mid);
      this.build(arr, 2 * node + 2, mid + 1, end);
      this.tree[node] = Math.min(this.tree[2 * node + 1], this.tree[2 * node + 2]);
    }
  }

  query(node, start, end, l, r) {
    if (r < start || end < l) return Infinity;
    if (l <= start && end <= r) return this.tree[node];

    const mid = Math.floor((start + end) / 2);
    const left = this.query(2 * node + 1, start, mid, l, r);
    const right = this.query(2 * node + 2, mid + 1, end, l, r);
    return Math.min(left, right);
  }

  minRange(l, r) {
    return this.query(0, 0, this.n - 1, l, r);
  }
}

// Example usage:
const arr = [1, 3, 2, 5, 4];
const segTree = new SegmentTree(arr);
console.log(segTree.minRange(1, 3)); // Output: 2

3. Implementing Range Maximum Queries

Operation Description Time Complexity Space Complexity
buildTree Constructs the segment tree from input array O(n) O(n)
rangeMaxQuery Queries maximum value in a given range [l, r] O(log n) O(1)
updateValue Updates value at a specific index O(log n) O(1)

Example: Range Maximum Query Implementation

class SegmentTree {
  constructor(arr) {
    this.n = arr.length;
    this.tree = new Array(4 * this.n).fill(0);
    this.build(arr, 0, 0, this.n - 1);
  }

  build(arr, node, start, end) {
    if (start === end) {
      this.tree[node] = arr[start];
    } else {
      const mid = Math.floor((start + end) / 2);
      this.build(arr, 2 * node + 1, start, mid);
      this.build(arr, 2 * node + 2, mid + 1, end);
      this.tree[node] = Math.max(this.tree[2 * node + 1], this.tree[2 * node + 2]);
    }
  }

  query(node, start, end, l, r) {
    if (r < start || end < l) return -Infinity;
    if (l <= start && end <= r) return this.tree[node];

    const mid = Math.floor((start + end) / 2);
    const left = this.query(2 * node + 1, start, mid, l, r);
    const right = this.query(2 * node + 2, mid + 1, end, l, r);
    return Math.max(left, right);
  }

  rangeMaxQuery(l, r) {
    return this.query(0, 0, this.n - 1, l, r);
  }
}

// Example usage:
const arr = [1, 3, 5, 7, 9, 11];
const segTree = new SegmentTree(arr);
console.log(segTree.rangeMaxQuery(1, 4)); // Output: 9

4. Implementing Point Updates

Segment trees support efficient point updates and range queries. When updating a point in the array, we must propagate changes up the tree to maintain correctness.

Operation Description Time Complexity Space Complexity
update(index, value) Updates the value at a specific index in the original array O(log n) O(1)
buildTree(array) Initializes the segment tree from an array O(n) O(n)

Example: Point Update Implementation

class SegmentTree {
  constructor(arr) {
    this.n = arr.length;
    this.tree = new Array(4 * this.n).fill(0);
    this.build(arr, 0, 0, this.n - 1);
  }

  build(arr, node, start, end) {
    if (start === end) {
      this.tree[node] = arr[start];
    } else {
      const mid = Math.floor((start + end) / 2);
      this.build(arr, 2 * node + 1, start, mid);
      this.build(arr, 2 * node + 2, mid + 1, end);
      this.tree[node] = this.tree[2 * node + 1] + this.tree[2 * node + 2];
    }
  }

  update(node, start, end, index, value) {
    if (start === end) {
      this.tree[node] = value;
    } else {
      const mid = Math.floor((start + end) / 2);
      if (index <= mid) {
        this.update(2 * node + 1, start, mid, index, value);
      } else {
        this.update(2 * node + 2, mid + 1, end, index, value);
      }
      this.tree[node] = this.tree[2 * node + 1] + this.tree[2 * node + 2];
    }
  }

  query(node, start, end, l, r) {
    if (r < start || end < l) return 0;
    if (l <= start && end <= r) return this.tree[node];
    const mid = Math.floor((start + end) / 2);
    return this.query(node * 2 + 1, start, mid, l, r) +
           this.query(node * 2 + 2, mid + 1, end, l, r);
  }
}

// Usage example:
const arr = [1, 3, 5, 7, 9, 11];
const segTree = new SegmentTree(arr);
segTree.update(0, 0, arr.length - 1, 1, 10); // Update index 1 to value 10
console.log(segTree.query(0, 0, arr.length - 1, 0, 2)); // Sum from index 0 to 2
Note: The update operation modifies only the necessary nodes in the segment tree to reflect the new value at a specific index. The propagation happens from leaf to root, ensuring that parent nodes correctly represent the sum of their children.

5. Implementing Range Updates

Operation Description Time Complexity Space Complexity
updateRange(tree, l, r, value) Updates all elements in range [l, r] with a given value O(log n) O(1)
rangeUpdate(tree, l, r, value) Applies a value to all elements in range [l, r] O(log n) O(1)

Example: Range Update Implementation

class SegmentTree {
  constructor(arr) {
    this.n = arr.length;
    this.tree = new Array(4 * this.n).fill(0);
    this.lazy = new Array(4 * this.n).fill(0);
    this.build(arr, 0, 0, this.n - 1);
  }

  build(arr, node, start, end) {
    if (start === end) {
      this.tree[node] = arr[start];
    } else {
      const mid = Math.floor((start + end) / 2);
      this.build(arr, 2 * node + 1, start, mid);
      this.build(arr, 2 * node + 2, mid + 1, end);
      this.tree[node] = this.tree[2 * node + 1] + this.tree[2 * node + 2];
    }
  }

  push(node, start, end) {
    if (this.lazy[node] !== 0) {
      this.tree[node] += this.lazy[node] * (end - start + 1);
      if (start !== end) {
        this.lazy[2 * node + 1] += this.lazy[node];
        this.lazy[2 * node + 2] += this.lazy[node];
      }
      this.lazy[node] = 0;
    }
  }

  updateRange(node, start, end, l, r, value) {
    this.push(node, start, end);
    if (start > r || end < l) return;
    if (start >= l && end <= r) {
      this.lazy[node] += value;
      this.push(node, start, end);
      return;
    }
    const mid = Math.floor((start + end) / 2);
    this.updateRange(2 * node + 1, start, mid, l, r, value);
    this.updateRange(2 * node + 2, mid + 1, end, l, r, value);
    this.push(2 * node + 1, start, mid);
    this.push(2 * node + 2, mid + 1, end);
    this.tree[node] = this.tree[2 * node + 1] + this.tree[2 * node + 2];
  }
}

// Usage example:
const arr = [1, 3, 5, 7, 9, 11];
const segTree = new SegmentTree(arr);
segTree.updateRange(0, 0, arr.length - 1, 1, 3, 2); // Add 2 to indices 1-3
Note: Lazy propagation is essential for efficient range updates in segment trees to avoid O(n) operations per update.

6. Solving Range GCD Queries

Operation Description Time Complexity Space Complexity
Build Segment Tree Constructs a segment tree from an array of integers to support range GCD queries O(n) O(n)
Range GCD Query Computes the GCD of elements in a given range [l, r] O(log n) O(1) (excluding tree storage)
Update Element Updates the value at a specific index in the array O(log n) O(1) (excluding tree storage)

Example: Building and Querying a Segment Tree for GCD

function buildSegmentTree(arr) {
  const n = arr.length;
  const tree = new Array(4 * n).fill(0);
  
  function build(node, start, end) {
    if (start === end) {
      tree[node] = arr[start];
    } else {
      const mid = Math.floor((start + end) / 2);
      build(2 * node, start, mid);
      build(2 * node + 1, mid + 1, end);
      tree[node] = gcd(tree[2 * node], tree[2 * node + 1]);
    }
  }

  build(1, 0, n - 1);
  return tree;
}

function query(tree, node, start, end, l, r) {
  if (r < start || end < l) return 0;
  if (l <= start && end <= r) return tree[node];

  const mid = Math.floor((start + end) / 2);
  const leftGcd = query(tree, 2 * node, start, mid, l, r);
  const rightGcd = query(tree, 2 * node + 1, mid + 1, end, l, r);
  return gcd(leftGcd, rightGcd);
}

function gcd(a, b) {
  while (b !== 0) {
    [a, b] = [b, a % b];
  }
  return a;
}

// Example usage:
const arr = [2, 4, 6, 8, 10];
const tree = buildSegmentTree(arr);
console.log(query(tree, 1, 0, arr.length - 1, 1, 3)); // GCD of [4, 6, 8] = 2
Note: The segment tree is built using a binary heap-like structure where each node stores the GCD of its children. The query operation uses a divide-and-conquer approach to efficiently compute the GCD over a range.

7. Implementing Range Assignment

Operation Description Time Complexity Space Complexity
rangeAssign(l, r, value) Assigns a new value to all elements in the range [l, r] O(log n) O(n)
rangeUpdate(l, r, value) Updates all elements in the range [l, r] with a new value O(log n) O(n)

Example: Range Assignment Implementation

class SegmentTree {
  constructor(size) {
    this.size = size;
    this.tree = new Array(4 * size).fill(0);
    this.lazy = new Array(4 * size).fill(0);
  }

  pushDown(node, start, end) {
    if (this.lazy[node] !== 0 && start !== end) {
      this.tree[2 * node] = this.lazy[node];
      this.tree[2 * node + 1] = this.lazy[node];
      this.lazy[2 * node] = this.lazy[node];
      this.lazy[2 * node + 1] = this.lazy[node];
      this.lazy[node] = 0;
    }
  }

  rangeAssign(l, r, value, node = 1, start = 0, end = this.size - 1) {
    if (l > end || r < start) return;
    if (l <= start && end <= r) {
      this.tree[node] = value;
      this.lazy[node] = value;
      return;
    }
    this.pushDown(node, start, end);
    const mid = Math.floor((start + end) / 2);
    this.rangeAssign(l, r, value, 2 * node, start, mid);
    this.rangeAssign(l, r, value, 2 * node + 1, mid + 1, end);
    this.tree[node] = this.tree[2 * node] + this.tree[2 * node + 1];
  }

  query(l, r, node = 1, start = 0, end = this.size - 1) {
    if (l > end || r < start) return 0;
    if (l <= start && end <= r) return this.tree[node];
    this.pushDown(node, start, end);
    const mid = Math.floor((start + end) / 2);
    return this.query(l, r, 2 * node, start, mid) +
           this.query(l, r, 2 * node + 1, mid + 1, end);
  }
}

// Usage example:
const segTree = new SegmentTree(10);
segTree.rangeAssign(2, 5, 100); // Assigns value 100 to indices 2 through 5
console.log(segTree.query(2, 3)); // Outputs 100
Note: Lazy propagation is essential for efficient range assignment operations in segment trees.

8. Solving Count of Elements

Operation Description Time Complexity Space Complexity
countInRange Counts elements in a range [l, r] O(log n) O(n)
countLessThan Counts elements less than a value O(log n) O(n)
countGreaterThan Counts elements greater than a value O(log n) O(n)

Example: Count Elements in Range

const segmentTree = new SegmentTree([1, 3, 5, 7, 9]);
const count = segmentTree.countInRange(1, 3); // Returns 2 (elements 3 and 5)
Note: The segment tree must be built with the array elements to support range queries.

9. Implementing Historic Queries

Operation Description Time Complexity Space Complexity
queryRange Retrieve sum/minimum/maximum in a given range O(log n) O(n)
updateValue Update value at a specific index O(log n) O(n)
rangeQuery Get aggregated value over a range (e.g., sum, min) O(log n) O(n)

Example: Range Sum Query with Segment Tree

class SegmentTree {
  constructor(arr) {
    this.n = arr.length;
    this.tree = new Array(4 * this.n).fill(0);
    this.build(arr, 0, 0, this.n - 1);
  }

  build(arr, node, start, end) {
    if (start === end) {
      this.tree[node] = arr[start];
    } else {
      const mid = Math.floor((start + end) / 2);
      this.build(arr, 2 * node + 1, start, mid);
      this.build(arr, 2 * node + 2, mid + 1, end);
      this.tree[node] = this.tree[2 * node + 1] + this.tree[2 * node + 2];
    }
  }

  queryRange(node, start, end, l, r) {
    if (r < start || end < l) return 0;
    if (l <= start && end <= r) return this.tree[node];

    const mid = Math.floor((start + end) / 2);
    return this.queryRange(2 * node + 1, start, mid, l, r) +
           this.queryRange(2 * node + 2, mid + 1, end, l, r);
  }

  update(node, start, end, idx, val) {
    if (start === end) {
      this.tree[node] = val;
    } else {
      const mid = Math.floor((start + end) / 2);
      if (idx <= mid) {
        this.update(2 * node + 1, start, mid, idx, val);
      } else {
        this.update(2 * node + 2, mid + 1, end, idx, val);
      }
      this.tree[node] = this.tree[2 * node + 1] + this.tree[2 * node + 2];
    }
  }

  sumRange(left, right) {
    return this.queryRange(0, 0, this.n - 1, left, right);
  }

  updateValue(index, value) {
    this.update(0, 0, this.n - 1, index, value);
  }
}

// Usage Example
const arr = [1, 3, 5, 7, 9, 11];
const segTree = new SegmentTree(arr);
console.log(segTree.sumRange(1, 3)); // Output: 15 (3 + 5 + 7)
segTree.updateValue(1, 10); // Update index 1 to 10
console.log(segTree.sumRange(1, 3)); // Output: 22 (10 + 5 + 7)
Note: Segment trees are ideal for handling frequent range queries and updates efficiently, especially in problems involving dynamic arrays where both operations must be fast.

10. Solving 2D Range Queries

A Segment Tree can be extended to handle 2D range queries by building a tree of trees. Each node in the primary segment tree represents a vertical slice of the 2D space, and each such node contains another segment tree for horizontal queries.

Operation Description Time Complexity Space Complexity
Build 2D Segment Tree Constructs a 2D segment tree from a 2D array of size n×m O(n * m) O(n * m)
Query 2D Range Computes a query over a rectangular region in the 2D array O(log n * log m) O(1)
Update Point Updates a single element in the 2D array O(log n * log m) O(1)

Example: 2D Range Sum Query

class SegmentTree2D {
    constructor(matrix) {
        this.n = matrix.length;
        this.m = matrix[0].length;
        this.tree = Array(this.n * 4).fill().map(() => Array(this.m * 4).fill(0));
        this.build(matrix, 0, 0, this.n - 1, this.m - 1, 1, 1);
    }

    build(matrix, rs, re, cs, ce, nodeRow, nodeCol) {
        if (rs > re || cs > ce) return;
        if (rs === re && cs === ce) {
            this.tree[nodeRow][nodeCol] = matrix[rs][cs];
            return;
        }

        const rm = Math.floor((rs + re) / 2);
        const cm = Math.floor((cs + ce) / 2);

        this.build(matrix, rs, rm, cs, cm, nodeRow * 2, nodeCol * 2);
        this.build(matrix, rs, rm, cm + 1, ce, nodeRow * 2, nodeCol * 2 + 1);
        this.build(matrix, rm + 1, re, cs, cm, nodeRow * 2 + 1, nodeCol * 2);
        this.build(matrix, rm + 1, re, cm + 1, ce, nodeRow * 2 + 1, nodeCol * 2 + 1);

        this.tree[nodeRow][nodeCol] = this.tree[nodeRow * 2][nodeCol * 2] +
            this.tree[nodeRow * 2][nodeCol * 2 + 1] +
            this.tree[nodeRow * 2 + 1][nodeCol * 2] +
            this.tree[nodeRow * 2 + 1][nodeCol * 2 + 1];
    }

    query(rs, re, cs, ce, nodeRow, nodeCol, rsq, req, csq, ceq) {
        if (rs > re || cs > ce || rsq > req || csq > ceq) return 0;
        if (rs > rsq || re < req || cs > csq || ce < ceq) return 0;
        if (rsq <= rs && req >= re && csq <= cs && ceq >= ce) {
            return this.tree[nodeRow][nodeCol];
        }

        const rm = Math.floor((rs + re) / 2);
        const cm = Math.floor((cs + ce) / 2);

        return this.query(rs, rm, cs, cm, nodeRow * 2, nodeCol * 2, rsq, req, csq, ceq) +
            this.query(rs, rm, cm + 1, ce, nodeRow * 2, nodeCol * 2 + 1, rsq, req, csq, ceq) +
            this.query(rm + 1, re, cs, cm, nodeRow * 2 + 1, nodeCol * 2, rsq, req, csq, ceq) +
            this.query(rm + 1, re, cm + 1, ce, nodeRow * 2 + 1, nodeCol * 2 + 1, rsq, req, csq, ceq);
    }

    update(row, col, value) {
        this.updatePoint(row, col, value, 0, 0, this.n - 1, this.m - 1, 1, 1);
    }

    updatePoint(row, col, value, rs, re, cs, ce, nodeRow, nodeCol) {
        if (rs > re || cs > ce) return;
        if (rs === re && cs === ce) {
            this.tree[nodeRow][nodeCol] = value;
            return;
        }

        const rm = Math.floor((rs + re) / 2);
        const cm = Math.floor((cs + ce) / 2);

        if (row <= rm && col <= cm) {
            this.updatePoint(row, col, value, rs, rm, cs, cm, nodeRow * 2, nodeCol * 2);
        } else if (row <= rm && col > cm) {
            this.updatePoint(row, col, value, rs, rm, cm + 1, ce, nodeRow * 2, nodeCol * 2 + 1);
        } else if (row > rm && col <= cm) {
            this.updatePoint(row, col, value, rm + 1, re, cs, cm, nodeRow * 2 + 1, nodeCol * 2);
        } else {
            this.updatePoint(row, col, value, rm + 1, re, cm + 1, ce, nodeRow * 2 + 1, nodeCol * 2 + 1);
        }

        this.tree[nodeRow][nodeCol] = this.tree[nodeRow * 2][nodeCol * 2] +
            this.tree[nodeRow * 2][nodeCol * 2 + 1] +
            this.tree[nodeRow * 2 + 1][nodeCol * 2] +
            this.tree[nodeRow * 2 + 1][nodeCol * 2 + 1];
    }
}

// Example usage:
const matrix = [
    [1, 2, 3],
    [4, 5, 6],
    [7, 8, 9]
];
const segTree2D = new SegmentTree2D(matrix);
console.log(segTree2D.query(0, 1, 0, 1, 1, 1, 0, 1, 0, 1)); // Sum of 2x2 submatrix
Note: The 2D segment tree is particularly useful for problems involving dynamic updates and range queries in a 2D grid, such as calculating the sum of elements in a rectangular region.

11. Optimizing Space Usage

Operation Space Complexity Notes
Standard Segment Tree O(4n) Typical implementation uses 4n nodes for safety
Optimized Segment Tree O(2n) Uses 2n nodes by avoiding over-allocation
Lazy Propagation O(2n) Same as optimized, but with additional lazy nodes

Example: Space-Optimized Segment Tree

class OptimizedSegmentTree {
  constructor(arr) {
    this.n = arr.length;
    this.tree = new Array(2 * this.n).fill(0);
    this.build(arr);
  }

  build(arr) {
    for (let i = 0; i < this.n; i++) {
      this.tree[this.n + i] = arr[i];
    }
    for (let i = this.n - 1; i > 0; i--) {
      this.tree[i] = this.tree[2 * i] + this.tree[2 * i + 1];
    }
  }

  query(l, r) {
    let res = 0;
    l += this.n;
    r += this.n;
    while (l <= r) {
      if (l % 2 === 1) res += this.tree[l++];
      if (r % 2 === 0) res += this.tree[r--];
      l /= 2;
      r /= 2;
    }
    return res;
  }

  update(pos, val) {
    pos += this.n;
    this.tree[pos] = val;
    while (pos > 1) {
      pos /= 2;
      this.tree[pos] = this.tree[2 * pos] + this.tree[2 * pos + 1];
    }
  }
}
Note: The optimized version uses exactly 2n nodes, reducing memory overhead compared to the standard O(4n) approach.

12. Combining Multiple Operations

Example: Range Sum and Update

class SegmentTree {
  constructor(arr) {
    this.n = arr.length;
    this.tree = new Array(4 * this.n).fill(0);
    this.build(arr, 0, 0, this.n - 1);
  }

  build(arr, node, start, end) {
    if (start === end) {
      this.tree[node] = arr[start];
    } else {
      const mid = Math.floor((start + end) / 2);
      this.build(arr, 2 * node + 1, start, mid);
      this.build(arr, 2 * node + 2, mid + 1, end);
      this.tree[node] = this.tree[2 * node + 1] + this.tree[2 * node + 2];
    }
  }

  update(node, start, end, idx, val) {
    if (start === end) {
      this.tree[node] = val;
    } else {
      const mid = Math.floor((start + end) / 2);
      if (idx <= mid) {
        this.update(2 * node + 1, start, mid, idx, val);
      } else {
        this.update(2 * node + 2, mid + 1, end, idx, val);
      }
      this.tree[node] = this.tree[2 * node + 1] + this.tree[2 * node + 2];
    }
  }

  query(node, start, end, l, r) {
    if (r < start || l > end) return 0;
    if (l <= start && end <= r) return this.tree[node];
    const mid = Math.floor((start + end) / 2);
    return this.query(2 * node + 1, start, mid, l, r) +
           this.query(2 * node + 2, mid + 1, end, l, r);
  }
}

const arr = [1, 3, 5, 7, 9, 11];
const segTree = new SegmentTree(arr);
console.log(segTree.query(0, 0, 5, 1, 3)); // Output: 15 (3 + 5 + 7)
segTree.update(0, 0, 5, 1, 10);
console.log(segTree.query(0, 0, 5, 1, 3)); // Output: 22 (10 + 5 + 7)
Operation Description Time Complexity Space Complexity
Range Sum Query Computes sum over a range [l, r] O(log n) O(n)
Point Update Updates value at a specific index O(log n) O(n)
Note: Segment trees can be adapted to support various operations like min, max, and GCD by modifying the merge function.

Was this article helpful?