Skip to main content
Ajay Dhangar
EditReport

Segment Tree

A Segment Tree is an advanced data structure that allows you to efficiently perform range queries and point updates on an array โ€” operations that would take O(n) per query with brute force, reduced to O(log n) with a Segment Tree.


๐Ÿง  Why Segment Tree?โ€‹

Problem: Given array [1, 3, 5, 7, 9, 11]
Answer Q queries: "What is the sum of elements from index L to R?"
Also handle U updates: "Change element at index i to value v"

โŒ BRUTE FORCE:
Query: O(n) per query โ†’ 100 queries on 10โถ array = 10โธ operations (TLE)
Update: O(1)

โœ… SEGMENT TREE:
Build: O(n)
Query: O(log n)
Update: O(log n)

๐ŸŒณ How the Tree is Builtโ€‹

A Segment Tree maps an array into a binary tree where:

  • Each leaf node stores one element
  • Each internal node stores the result (sum/min/max) of its children's range
Array: [1, 3, 5, 7, 9, 11]
Index: 0 1 2 3 4 5

[0,5] = 36
/ \
[0,2] = 9 [3,5] = 27
/ \ / \
[0,1]=4 [2,2]=5 [3,4]=16 [5,5]=11
/ \ / \
[0,0]=1 [1,1]=3 [3,3]=7 [4,4]=9

Node stores: (range, value)

Array Representation of Treeโ€‹

For array of size n, segment tree uses array of size 4*n

tree[1] = root = sum of entire array
tree[2] = left child (indices 0 to mid)
tree[3] = right child (indices mid+1 to end)
tree[4] = left child of tree[2]
tree[5] = right child of tree[2]
...and so on

For node at index i:
Left child โ†’ 2*i
Right child โ†’ 2*i + 1
Parent โ†’ i/2

๐Ÿ”จ Operation 1: Buildโ€‹

Visual Step-by-Stepโ€‹

Array: [1, 3, 5, 7, 9, 11]

Step 1: Fill leaf nodes
tree[8]=1, tree[9]=3, tree[5]=5, tree[10]=7, tree[11]=9, tree[7]=11

Step 2: Fill internal nodes bottom-up
tree[4] = tree[8]+tree[9] = 1+3 = 4
tree[6] = tree[10]+tree[11] = 7+9 = 16
tree[2] = tree[4]+tree[5] = 4+5 = 9
tree[3] = tree[6]+tree[7] = 16+11 = 27
tree[1] = tree[2]+tree[3] = 9+27 = 36

Correct tree (1-indexed, nodes store range sum):
tree[1] = 36 (range [0,5])
tree[2] = 9 (range [0,2])
tree[3] = 27 (range [3,5])
tree[4] = 4 (range [0,1])
tree[5] = 5 (range [2,2])
tree[6] = 16 (range [3,4])
tree[7] = 11 (range [5,5])
tree[8] = 1 (range [0,0])
tree[9] = 3 (range [1,1])
tree[10] = 7 (range [3,3])
tree[11] = 9 (range [4,4])

Codeโ€‹

class SegmentTree:
def __init__(self, arr):
self.n = len(arr)
self.tree = [0] * (4 * self.n)
self.build(arr, 0, self.n - 1, 1)

def build(self, arr, start, end, node):
if start == end:
# Leaf node โ€” store the element
self.tree[node] = arr[start]
else:
mid = (start + end) // 2
left_child = 2 * node
right_child = 2 * node + 1

# Recursively build left and right subtrees
self.build(arr, start, mid, left_child)
self.build(arr, mid + 1, end, right_child)

# Internal node stores sum of children
self.tree[node] = self.tree[left_child] + self.tree[right_child]

Time Complexity: O(n) ย |ย  Space Complexity: O(n)


๐Ÿ” Operation 2: Range Queryโ€‹

Visual Dry-Runโ€‹

Array: [1, 3, 5, 7, 9, 11]   Query: sum(L=1, R=4)  Expected: 3+5+7+9 = 24

Start at root node [0,5]:
Query [1,4] overlaps [0,5] โ†’ go to both children

Left child [0,2]:
Query [1,4] overlaps [0,2] โ†’ go to both children

Left child [0,1]:
Query [1,4] overlaps [0,1] โ†’ go to both children

Left child [0,0]:
Query [1,4] does NOT overlap [0,0] โ†’ return 0 โŒ

Right child [1,1]:
Query [1,4] FULLY covers [1,1] โ†’ return tree value = 3 โœ…

Right child [2,2]:
Query [1,4] FULLY covers [2,2] โ†’ return tree value = 5 โœ…

Right child [3,5]:
Query [1,4] overlaps [3,5] โ†’ go to both children

Left child [3,4]:
Query [1,4] FULLY covers [3,4] โ†’ return tree value = 16 โœ…

Right child [5,5]:
Query [1,4] does NOT overlap [5,5] โ†’ return 0 โŒ

Final Answer: 0 + 3 + 5 + 16 + 0 = 24 โœ…

3 Cases in Queryโ€‹

1. NO OVERLAP    โ†’ current range is completely outside query โ†’ return 0
2. FULL OVERLAP โ†’ current range is completely inside query โ†’ return tree[node]
3. PARTIAL โ†’ current range partially overlaps query โ†’ recurse on children

Codeโ€‹

    def query(self, l, r, start, end, node):
# Case 1: No overlap
if r < start or end < l:
return 0

# Case 2: Full overlap
if l <= start and end <= r:
return self.tree[node]

# Case 3: Partial overlap โ€” recurse on both children
mid = (start + end) // 2
left_sum = self.query(l, r, start, mid, 2 * node)
right_sum = self.query(l, r, mid + 1, end, 2 * node + 1)
return left_sum + right_sum

# Usage: st.query(1, 4, 0, n-1, 1) โ†’ sum from index 1 to 4

Time Complexity: O(log n) ย |ย  Space Complexity: O(log n) recursion stack


โœ๏ธ Operation 3: Point Updateโ€‹

Visual Dry-Runโ€‹

Array: [1, 3, 5, 7, 9, 11]   Update: index=2, newValue=10
(Changing 5 โ†’ 10, so diff = +5)

Start at root [0,5]:
index=2 is in [0,5] โ†’ go to both children

Left child [0,2]:
index=2 is in [0,2] โ†’ go to right child only

Right child [2,2]:
index=2 == [2,2] โ†’ LEAF NODE
tree[node] = 10 โœ… (update leaf)

Back at [0,2]: tree[node] = tree[left] + tree[right] = 4 + 10 = 14 โœ…
Back at [0,5]: tree[node] = 14 + 27 = 41 โœ…

All ancestor nodes updated automatically!

Codeโ€‹

    def update(self, idx, new_val, start, end, node):
if start == end:
# Leaf node โ€” update value
self.tree[node] = new_val
else:
mid = (start + end) // 2
if idx <= mid:
# Index is in left subtree
self.update(idx, new_val, start, mid, 2 * node)
else:
# Index is in right subtree
self.update(idx, new_val, mid + 1, end, 2 * node + 1)

# Update current node after child is updated
self.tree[node] = self.tree[2 * node] + self.tree[2 * node + 1]

# Usage: st.update(2, 10, 0, n-1, 1) โ†’ change index 2 to value 10

Time Complexity: O(log n) ย |ย  Space Complexity: O(log n) recursion stack


โšก Advanced: Lazy Propagation (Range Update)โ€‹

The Problem Without Lazyโ€‹

Without lazy: "Add 5 to all elements from index 1 to 4"
โ†’ We'd call point update 4 times โ†’ O(4 log n)

With lazy propagation:
โ†’ Mark the range as "pending update" and defer it โ†’ O(log n)

Core Ideaโ€‹

Keep a separate lazy[] array.
lazy[node] = pending value to be added to all elements in this node's range.

When we visit a node:
1. Apply lazy[node] to tree[node] first
2. Pass lazy[node] down to children (propagate)
3. Clear lazy[node]

This way, we defer work until we actually need it.

Codeโ€‹

class SegmentTreeLazy:
def __init__(self, arr):
self.n = len(arr)
self.tree = [0] * (4 * self.n)
self.lazy = [0] * (4 * self.n) # pending updates
self.build(arr, 0, self.n - 1, 1)

def build(self, arr, start, end, node):
if start == end:
self.tree[node] = arr[start]
else:
mid = (start + end) // 2
self.build(arr, start, mid, 2 * node)
self.build(arr, mid + 1, end, 2 * node + 1)
self.tree[node] = self.tree[2 * node] + self.tree[2 * node + 1]

def propagate(self, node, start, end):
if self.lazy[node] != 0:
mid = (start + end) // 2
# Apply pending update to children
self.tree[2 * node] += self.lazy[node] * (mid - start + 1)
self.tree[2 * node + 1] += self.lazy[node] * (end - mid)
# Pass lazy to children
self.lazy[2 * node] += self.lazy[node]
self.lazy[2 * node + 1] += self.lazy[node]
# Clear current lazy
self.lazy[node] = 0

def range_update(self, l, r, val, start, end, node):
if r < start or end < l:
return
if l <= start and end <= r:
# Full overlap โ€” update and mark lazy
self.tree[node] += val * (end - start + 1)
self.lazy[node] += val
return
# Partial overlap โ€” propagate first then recurse
self.propagate(node, start, end)
mid = (start + end) // 2
self.range_update(l, r, val, start, mid, 2 * node)
self.range_update(l, r, val, mid + 1, end, 2 * node + 1)
self.tree[node] = self.tree[2 * node] + self.tree[2 * node + 1]

def query(self, l, r, start, end, node):
if r < start or end < l:
return 0
if l <= start and end <= r:
return self.tree[node]
# Propagate before querying children
self.propagate(node, start, end)
mid = (start + end) // 2
return (self.query(l, r, start, mid, 2 * node) +
self.query(l, r, mid + 1, end, 2 * node + 1))

Time Complexity: O(log n) per range update and query ย |ย  Space Complexity: O(n)


๐Ÿ“Š Complexity Summaryโ€‹

OperationWithout Segment TreeWith Segment TreeWith Lazy Propagation
BuildO(1)O(n)O(n)
Range QueryO(n)O(log n)O(log n)
Point UpdateO(1)O(log n)O(log n)
Range UpdateO(n)O(n log n)O(log n)

โŒ Common Mistakesโ€‹

  1. Tree array too small โ€” Always allocate 4 * n size for the tree array, not 2 * n.
  2. Starting node at 0 โ€” Segment trees use 1-indexed nodes. Left child = 2*i, right = 2*i+1 only works when root is at index 1.
  3. Forgetting to propagate lazy โ€” In lazy propagation, always call propagate() before recursing into children during both query and update.
  4. Integer overflow โ€” For large arrays with large values, use long long (C++) or long (Java) for the tree array.
  5. Wrong base case in query โ€” No-overlap must return the identity element (0 for sum, INT_MAX for min, INT_MIN for max) not -1.

๐Ÿ‹๏ธ Practice Problemsโ€‹

#ProblemOperationDifficulty
1Range Sum Query โ€” MutablePoint Update + Range Query๐ŸŸก Medium
2Count of Smaller Numbers After SelfRange Query๐Ÿ”ด Hard
3Range Minimum QueryRange Query๐ŸŸก Medium
4Interval Sum (Range Update)Lazy Propagation๐ŸŸก Medium
5Number of Longest Increasing SubsequenceRange Query๐Ÿ”ด Hard
6My Calendar IIIRange Update + Range Query๐Ÿ”ด Hard
7Rectangle Area IICoordinate Compression + Segment Tree๐Ÿ”ด Hard

๐ŸŒ Real-World Applicationsโ€‹

  • Database indexing โ€” Range aggregate queries (SUM, MIN, MAX) over rows
  • Competitive programming โ€” Most Codeforces Div. 1 problems involving range queries
  • Geographic Information Systems (GIS) โ€” Range queries on spatial data
  • Network routing โ€” Finding minimum latency path in a range

๐Ÿ”— Referencesโ€‹

Finished reading? Mark this topic as complete.