Segment Tree
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.
Video Explanationโ

๐ง 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โ
- Python
- Java
- C++
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]
public class SegmentTree {
long[] tree;
int n;
public SegmentTree(int[] arr) {
n = arr.length;
tree = new int[4 * n];
build(arr, 0, n - 1, 1);
}
private void build(int[] arr, int start, int end, int node) {
if (start == end) {
// Leaf node โ store the element
tree[node] = arr[start];
} else {
int mid = (start + end) / 2;
int leftChild = 2 * node;
int rightChild = 2 * node + 1;
// Recursively build left and right subtrees
build(arr, start, mid, leftChild);
build(arr, mid + 1, end, rightChild);
// Internal node stores sum of children
tree[node] = tree[leftChild] + tree[rightChild];
}
}
}
#include <bits/stdc++.h>
using namespace std;
class SegmentTree {
public:
vector<long long> tree;
int n;
SegmentTree(vector<int>& arr) {
n = arr.size();
tree.resize(4 * n, 0);
build(arr, 0, n - 1, 1);
}
void build(vector<int>& arr, int start, int end, int node) {
if (start == end) {
// Leaf node โ store the element
tree[node] = arr[start];
} else {
int mid = (start + end) / 2;
int leftChild = 2 * node;
int rightChild = 2 * node + 1;
// Recursively build left and right subtrees
build(arr, start, mid, leftChild);
build(arr, mid + 1, end, rightChild);
// Internal node stores sum of children
tree[node] = tree[leftChild] + tree[rightChild];
}
}
};
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โ
- Python
- Java
- C++
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
public long query(int l, int r, int start, int end, int node) {
// Case 1: No overlap
if (r < start || end < l) return 0;
// Case 2: Full overlap
if (l <= start && end <= r) return tree[node];
// Case 3: Partial overlap โ recurse on both children
int mid = (start + end) / 2;
int leftSum = query(l, r, start, mid, 2 * node);
int rightSum = query(l, r, mid + 1, end, 2 * node + 1);
return leftSum + rightSum;
}
// Usage: st.query(1, 4, 0, n-1, 1) โ sum from index 1 to 4
int query(int l, int r, int start, int end, int node) {
// Case 1: No overlap
if (r < start || end < l) return 0;
// Case 2: Full overlap
if (l <= start && end <= r) return tree[node];
// Case 3: Partial overlap โ recurse on both children
int mid = (start + end) / 2;
int leftSum = query(l, r, start, mid, 2 * node);
int rightSum = query(l, r, mid + 1, end, 2 * node + 1);
return leftSum + rightSum;
}
// 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โ
- Python
- Java
- C++
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
public void update(int idx, int newVal, int start, int end, int node) {
if (start == end) {
// Leaf node โ update value
tree[node] = newVal;
} else {
int mid = (start + end) / 2;
if (idx <= mid) {
// Index is in left subtree
update(idx, newVal, start, mid, 2 * node);
} else {
// Index is in right subtree
update(idx, newVal, mid + 1, end, 2 * node + 1);
}
// Update current node after child is updated
tree[node] = tree[2 * node] + tree[2 * node + 1];
}
}
// Usage: st.update(2, 10, 0, n-1, 1) โ change index 2 to value 10
void update(int idx, int newVal, int start, int end, int node) {
if (start == end) {
// Leaf node โ update value
tree[node] = newVal;
} else {
int mid = (start + end) / 2;
if (idx <= mid) {
// Index is in left subtree
update(idx, newVal, start, mid, 2 * node);
} else {
// Index is in right subtree
update(idx, newVal, mid + 1, end, 2 * node + 1);
}
// Update current node after child is updated
tree[node] = tree[2 * node] + 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โ
- Python
- Java
- C++
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))
public class SegmentTreeLazy {
long[] tree, lazy;
int n;
public SegmentTreeLazy(int[] arr) {
n = arr.length;
tree = new long[4 * n];
lazy = new long[4 * n];
build(arr, 0, n - 1, 1);
}
private void build(int[] arr, int start, int end, int node) {
if (start == end) {
tree[node] = arr[start];
} else {
int mid = (start + end) / 2;
build(arr, start, mid, 2 * node);
build(arr, mid + 1, end, 2 * node + 1);
tree[node] = tree[2 * node] + tree[2 * node + 1];
}
}
private void propagate(int node, int start, int end) {
if (lazy[node] != 0) {
int mid = (start + end) / 2;
tree[2 * node] += lazy[node] * (mid - start + 1);
tree[2 * node + 1] += lazy[node] * (end - mid);
lazy[2 * node] += lazy[node];
lazy[2 * node + 1] += lazy[node];
lazy[node] = 0;
}
}
public void rangeUpdate(int l, int r, int val, int start, int end, int node) {
if (r < start || end < l) return;
if (l <= start && end <= r) {
tree[node] += (long) val * (end - start + 1);
lazy[node] += val;
return;
}
propagate(node, start, end);
int mid = (start + end) / 2;
rangeUpdate(l, r, val, start, mid, 2 * node);
rangeUpdate(l, r, val, mid + 1, end, 2 * node + 1);
tree[node] = tree[2 * node] + tree[2 * node + 1];
}
public long query(int l, int r, int start, int end, int node) {
if (r < start || end < l) return 0;
if (l <= start && end <= r) return tree[node];
propagate(node, start, end);
int mid = (start + end) / 2;
return query(l, r, start, mid, 2 * node) +
query(l, r, mid + 1, end, 2 * node + 1);
}
}
#include <bits/stdc++.h>
using namespace std;
class SegmentTreeLazy {
public:
vector<long long> tree, lazy;
int n;
SegmentTreeLazy(vector<int>& arr) {
n = arr.size();
tree.resize(4 * n, 0);
lazy.resize(4 * n, 0);
build(arr, 0, n - 1, 1);
}
void build(vector<int>& arr, int start, int end, int node) {
if (start == end) {
tree[node] = arr[start];
} else {
int mid = (start + end) / 2;
build(arr, start, mid, 2 * node);
build(arr, mid + 1, end, 2 * node + 1);
tree[node] = tree[2 * node] + tree[2 * node + 1];
}
}
void propagate(int node, int start, int end) {
if (lazy[node] != 0) {
int mid = (start + end) / 2;
tree[2 * node] += lazy[node] * (mid - start + 1);
tree[2 * node + 1] += lazy[node] * (end - mid);
lazy[2 * node] += lazy[node];
lazy[2 * node + 1] += lazy[node];
lazy[node] = 0;
}
}
void rangeUpdate(int l, int r, int val, int start, int end, int node) {
if (r < start || end < l) return;
if (l <= start && end <= r) {
tree[node] += (long long)val * (end - start + 1);
lazy[node] += val;
return;
}
propagate(node, start, end);
int mid = (start + end) / 2;
rangeUpdate(l, r, val, start, mid, 2 * node);
rangeUpdate(l, r, val, mid + 1, end, 2 * node + 1);
tree[node] = tree[2 * node] + tree[2 * node + 1];
}
long long query(int l, int r, int start, int end, int node) {
if (r < start || end < l) return 0;
if (l <= start && end <= r) return tree[node];
propagate(node, start, end);
int mid = (start + end) / 2;
return query(l, r, start, mid, 2 * node) +
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โ
| Operation | Without Segment Tree | With Segment Tree | With Lazy Propagation |
|---|---|---|---|
| Build | O(1) | O(n) | O(n) |
| Range Query | O(n) | O(log n) | O(log n) |
| Point Update | O(1) | O(log n) | O(log n) |
| Range Update | O(n) | O(n log n) | O(log n) |
โ Common Mistakesโ
- Tree array too small โ Always allocate
4 * nsize for the tree array, not2 * n. - Starting node at 0 โ Segment trees use 1-indexed nodes. Left child =
2*i, right =2*i+1only works when root is at index 1. - Forgetting to propagate lazy โ In lazy propagation, always call
propagate()before recursing into children during both query and update. - Integer overflow โ For large arrays with large values, use
long long(C++) orlong(Java) for the tree array. - 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โ
| # | Problem | Operation | Difficulty |
|---|---|---|---|
| 1 | Range Sum Query โ Mutable | Point Update + Range Query | ๐ก Medium |
| 2 | Count of Smaller Numbers After Self | Range Query | ๐ด Hard |
| 3 | Range Minimum Query | Range Query | ๐ก Medium |
| 4 | Interval Sum (Range Update) | Lazy Propagation | ๐ก Medium |
| 5 | Number of Longest Increasing Subsequence | Range Query | ๐ด Hard |
| 6 | My Calendar III | Range Update + Range Query | ๐ด Hard |
| 7 | Rectangle Area II | Coordinate 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
๐ Related Topicsโ
- Disjoint Set Union (DSU): Manages dynamic connectivity and equivalence relations ().
- Trie: Tree structure specialized for efficient prefix lookup and string operations ().
- Dijkstra's Algorithm: Priority-queue based shortest path algorithm on graphs ().
๐ Referencesโ
Test Your Understanding
Answer these 3 questions to check what you have learned.
What is the main topic of this documentation page?
What should be identified before implementing segment tree?
How is understanding of segment tree best checked?
Done with this topic? Mark it as complete to track your progress.