When to Use Advanced Range Data Structures
A plain array supports point updates in O(1) and range queries (sum, min, max) in O(n). A prefix sum array supports range sum queries in O(1) but requires O(n) to rebuild after an update. When you need both efficient updates and efficient range queries, use a Segment Tree or Fenwick Tree (Binary Indexed Tree / BIT). Segment Tree: supports range queries and range updates in O(log n). More flexible — works for sum, min, max, GCD, XOR, and custom operations. Fenwick Tree: supports point updates and prefix queries in O(log n). Simpler to code, lower constant factor, but less flexible (sum/XOR only, not min/max). Rule of thumb: if you need min/max range queries or range updates: use a Segment Tree. If you only need point updates and prefix sum queries: use a Fenwick Tree.
Fenwick Tree (BIT) Implementation
A Fenwick Tree stores partial sums in a clever array structure. Each index i is responsible for a range of elements determined by the lowest set bit of i (i & -i). Point update and prefix sum query are both O(log n).
class FenwickTree:
def __init__(self, n):
self.n = n
self.tree = [0] * (n + 1)
def update(self, i, delta): # 1-indexed
while i 0:
s += self.tree[i]
i -= i & (-i) # move to responsible parent
return s
def range_query(self, l, r): # sum [l..r]
return self.query(r) - self.query(l - 1)
Use cases: LC 307 (Range Sum Query – Mutable): point update + range sum query. LC 315 (Count of Smaller Numbers After Self): for each element, query how many smaller elements have been inserted to the right. Process right to left; use BIT indexed by value. LC 493 (Reverse Pairs): similar pattern — count inversions using BIT on compressed values.
Segment Tree Implementation
A Segment Tree is a binary tree where each node covers a range [l, r]. Leaf nodes cover individual elements. Internal nodes store the aggregate (sum, min, max) of their range. Build in O(n); query and point update in O(log n). Range update with lazy propagation: O(log n) per range update.
class SegmentTree:
def __init__(self, nums):
self.n = len(nums)
self.tree = [0] * (4 * self.n)
self._build(nums, 1, 0, self.n - 1)
def _build(self, nums, node, start, end):
if start == end:
self.tree[node] = nums[start]
else:
mid = (start + end) // 2
self._build(nums, 2*node, start, mid)
self._build(nums, 2*node+1, mid+1, end)
self.tree[node] = self.tree[2*node] + self.tree[2*node+1]
def update(self, node, start, end, idx, val):
if start == end:
self.tree[node] = val
else:
mid = (start + end) // 2
if idx <= mid:
self.update(2*node, start, mid, idx, val)
else:
self.update(2*node+1, mid+1, end, idx, val)
self.tree[node] = self.tree[2*node] + self.tree[2*node+1]
def query(self, node, start, end, l, r):
if r < start or end < l: return 0
if l <= start and end <= r: return self.tree[node]
mid = (start + end) // 2
return (self.query(2*node, start, mid, l, r) +
self.query(2*node+1, mid+1, end, l, r))
Lazy Propagation for Range Updates
Lazy propagation defers range updates: instead of updating all O(n) elements in a range immediately, store a pending update (lazy tag) at the covering node and apply it when the node is visited. This enables O(log n) range updates. Pattern: maintain a lazy array alongside the tree. On range update [l, r, val]: update nodes covering [l, r] and set lazy[node] = val. On query or further update: push the lazy tag down to children before recursing. LC 218 (The Skyline Problem), LC 2407 (Longest Increasing Subsequence using BIT for optimization).
Order Statistics and Merge Sort Tree
Order statistics: find the k-th smallest element in a range, or count elements ≤ x in a range. Approach: Segment Tree with each node storing a sorted list of elements in its range (Merge Sort Tree). Build: O(n log n) space and time. Query (count elements ≤ x in [l, r]): O(log^2 n) using binary search at each node. Alternatively, use a Segment Tree on coordinate-compressed values: compress all values to [1..M], then for each position query/update by value. This pattern solves LC 315 (Count of Smaller Numbers After Self) and similar offline range query problems. Persistent Segment Tree: each update creates a new root, preserving history. Supports “k-th smallest in range [l, r]” queries in O(log n) using the difference between two versions.
See also: Meta Interview Prep
See also: Atlassian Interview Prep
See also: Databricks Interview Prep