18. Segment Tree#
Theory#
A segment tree is a binary tree where each node stores an aggregate (sum, min, max, gcd, etc.) over a contiguous range of the input array. It supports range queries and point updates in O(log n) time.
See also: Data Structures - Tree for general tree concepts.
When FAANG asks: range sum with point updates (LC 307) or range min/max queries on static arrays — segment tree or Fenwick tree. If the array is static, prefix sum is O(1) per query and simpler. Segment trees also appear in "count of smaller elements to the right" style problems (LC 315).
Lazy propagation extends the segment tree to range updates (add/multiply every element in a range) while keeping queries at O(log n). Each node stores a pending tag that is pushed to children only when needed.
Problems at a glance#
| LC | Problem | |
|---|---|---|
| 307 | Range Sum Query - Mutable | ↗ |
| 303 | Range Sum Query - Immutable | ↗ |
| 315 | Count of Smaller Numbers After Self | ↗ |
| 729 | My Calendar I | ↗ |
| 850 | Rectangle Area II | ↗ |
| 1622 | Fancy Sequence | ↗ |
Problems#
Problem 1: Range Sum Query - Mutable (Leetcode:307)#
Problem Statement
Given an integer array nums, handle multiple queries of the following types:
- Update the value of an element in
nums. - Calculate the sum of the elements of
numsbetween indicesleftandrightinclusive whereleft <= right.
Implement the NumArray class:
NumArray(int[] nums)Initializes the object with the integer arraynums.void update(int index, int val)Updates the value ofnums[index]to beval.int sumRange(int left, int right)Returns the sum of the elements ofnumsbetween indicesleftandrightinclusive (i.e.nums[left] + nums[left + 1] + ... + nums[right]).
Example 1:
Input
["NumArray", "sumRange", "update", "sumRange"]
[[[1, 3, 5]], [0, 2], [1, 2], [0, 2]]
Output
[null, 9, null, 8]Explanation
NumArray numArray = new NumArray([1, 3, 5]);
numArray.sumRange(0, 2); // return 1 + 3 + 5 = 9
numArray.update(1, 2); // nums = [1, 2, 5]
numArray.sumRange(0, 2); // return 1 + 2 + 5 = 8
Constraints:
1 <= nums.length <= 3 * 104-100 <= nums[i] <= 1000 <= index < nums.length-100 <= val <= 1000 <= left <= right < nums.length- At most
3 * 104calls will be made toupdateandsumRange.
Code and Explanation
- A Fenwick tree (Binary Indexed Tree) stores prefix sums in a compact array using the
i & -itrick to jump between responsible ranges. updateapplies a delta at one index; each affected tree cell is updated in O(log n).sumRange(left, right)returnsprefix(right) - prefix(left - 1), giving the inclusive range sum in O(log n).- Fenwick trees are the preferred choice for range sum + point update because they use less memory and simpler code than a segment tree.
Time: build O(n log n), update/query O(log n) | Space: O(n)
- Build an iterative segment tree where leaf nodes hold array values and each parent stores the sum of its two children.
- Point update: walk from the updated leaf to the root, recomputing sums at every ancestor — O(log n).
- Range sum query: decompose
[left, right]into O(log n) fully covered node ranges and add their stored sums. - This is the canonical segment tree template for range sum query with point updates.
Time: build O(n), update/query O(log n) | Space: O(n)
Problem 2: Range Sum Query - Immutable (Leetcode:303)#
Problem Statement
Given an integer array nums, handle multiple queries of the following type:
- Calculate the sum of the elements of
numsbetween indicesleftandrightinclusive whereleft <= right.
Implement the NumArray class:
NumArray(int[] nums)Initializes the object with the integer arraynums.int sumRange(int left, int right)Returns the sum of the elements ofnumsbetween indicesleftandrightinclusive (i.e.nums[left] + nums[left + 1] + ... + nums[right]).
Example 1:
Input
["NumArray", "sumRange", "sumRange", "sumRange"]
[[[-2, 0, 3, -5, 2, -1]], [0, 2], [2, 5], [0, 5]]
Output
[null, 1, -1, -3]Explanation
NumArray numArray = new NumArray([-2, 0, 3, -5, 2, -1]);
numArray.sumRange(0, 2); // return (-2) + 0 + 3 = 1
numArray.sumRange(2, 5); // return 3 + (-5) + 2 + (-1) = -1
numArray.sumRange(0, 5); // return (-2) + 0 + 3 + (-5) + 2 + (-1) = -3
Constraints:
1 <= nums.length <= 104-105 <= nums[i] <= 1050 <= left <= right < nums.length- At most
104calls will be made tosumRange.
Code and Explanation
- Precompute a prefix sum array where
prefix[i]is the sum ofnums[0..i-1]. - The sum of
nums[left..right]equalsprefix[right + 1] - prefix[left]— a single subtraction. - Because the array never changes, there is no need for a dynamic structure.
- This is the optimal solution: O(1) per query after O(n) preprocessing.
Time: build O(n), query O(1) | Space: O(n)
- Build a segment tree once at construction time — no updates are needed since the array is immutable.
- Each internal node stores the sum of its range; leaves hold individual elements.
sumRangewalks the tree in O(log n), merging fully covered child ranges.- Overkill for this problem, but demonstrates the static range sum query segment tree pattern used when updates will be added later (LC 307).
Time: build O(n), query O(log n) | Space: O(n)
Problem 3: Count of Smaller Numbers After Self (Leetcode:315)#
Problem Statement
Given an integer array nums, return an integer array counts where counts[i] is the number of smaller elements to the right of nums[i].
Example 1:
Input: nums = [5,2,6,1]
Output: [2,1,1,0]
Explanation:
To the right of 5 there are 2 smaller elements (2 and 1).
To the right of 2 there is only 1 smaller element (1).
To the right of 6 there is 1 smaller element (1).
To the right of 1 there is 0 smaller element.
Example 2:
Input: nums = [-1]
Output: [0]
Example 3:
Input: nums = [-1,-1]
Output: [0,0]
Constraints:
1 <= nums.length <= 105-104 <= nums[i] <= 104
Code and Explanation
- For each index
i, scan every element to its right and count how many are strictly smaller. - Direct translation of the problem statement — no extra data structures.
- Works correctly for all inputs but does not scale to n = 10⁵.
Time: O(n²) | Space: O(1) extra
- Coordinate-compress values to ranks 1..m so the Fenwick tree indexes fit in O(n) space.
- Process elements right to left: before inserting
nums[i], query how many ranks< rank(nums[i])are already in the tree — that is the answer for indexi. - Then
add(rank(nums[i]))to record this value for future leftward indices. - Each query and update is O(log n), giving O(n log n) total — the standard Fenwick tree inversion-count pattern.
Time: O(n log n) | Space: O(n)
Problem 4: Range Minimum Query - Mutable#
Problem Statement
Given an integer array nums, support two operations:
- Update the value at a single index.
- Query the minimum value in a range
[left, right]inclusive.
Implement:
RMQ(nums)— initialize with the array.update(index, val)— setnums[index] = val.queryMin(left, right)— returnmin(nums[left..right]).
Example:
Input: nums = [2, 5, 1, 9, 3]
queryMin(0, 4) → 1
update(2, 7) // nums = [2, 5, 7, 9, 3]
queryMin(0, 4) → 2
Constraints:
1 <= nums.length <= 105-109 <= nums[i] <= 109- At most
105update and query calls.
Code and Explanation
- Store the array and scan the requested range linearly on each query.
updateis O(1);queryMinis O(n) in the worst case.- Acceptable only for small arrays or very few queries — illustrates why a tree structure is needed.
Time: update O(1), query O(n) | Space: O(n)
- Build a segment tree where each node stores the minimum of its range instead of the sum.
- Point update: set the leaf and propagate
min(left, right)upward — O(log n). - Range min query: decompose
[left, right]into O(log n) fully covered nodes and take the minimum of their values. - This is the min analogue of LC 307 — same tree shape, different merge function (
minvs+).
Time: build O(n), update/query O(log n) | Space: O(n)
Problem 5: My Calendar I (Leetcode:729)#
Problem Statement
Implement the MyCalendar class to store your bookings. A booking can only be added if the requested time slot is not already booked.
MyCalendar()Initializes the calendar object.boolean book(int start, int end)Returnstrueif the event can be added to the calendar and does so. Otherwise, returnfalseand do not add the event. The half-open interval[start, end)is used.
Example 1:
Input: ["MyCalendar", "book", "book", "book"]
[[], [10, 20], [15, 25], [20, 30]]
Output: [null, true, false, true]
Explanation:
MyCalendar myCalendar = new MyCalendar();
myCalendar.book(10, 20); // return True
myCalendar.book(15, 25); // return False, time slot [15, 20) is already booked.
myCalendar.book(20, 30); // return True, time slot [20, 30) is available.
Constraints:
0 <= start < end <= 109- At most
1000calls will be made tobook.
Code and Explanation
- Store every confirmed booking as a
(start, end)pair. - Two intervals
[start, end)and[s, e)overlap iffstart < eands < end. - Check all existing bookings before accepting a new one.
- Simple and correct for the constraint of 1000 calls, but O(n) per booking.
Time: O(n) per book | Space: O(n)
- Use a segment tree over the timeline
[0, 10⁹]where each node stores whether its range is booked (max = 1) or free (max = 0). - Before booking
[start, end), query whether any part of that range is already occupied. - If free, update the range to mark it booked by setting values to 1.
- The tree is stored in a hash map (dynamic node allocation) since the domain is large but only O(k log W) nodes are ever created for k bookings.
Time: O(log W) per book, W = 10⁹ | Space: O(k log W)
Problem 6: Rectangle Area II (Leetcode:850)#
Problem Statement
You are given a 2D array of axis-aligned rectangles. Each rectangle[i] = [xi1, yi1, xi2, yi2] denotes the ith rectangle where (xi1, yi1) are the coordinates of the bottom-left corner, and (xi2, yi2) are the coordinates of the top-right corner.
Calculate the total area covered by all rectangles in the plane. Any area covered by two or more rectangles should only be counted once.
Return the total area. Since the answer may be too large, return it modulo** 109 + 7.
Example 1:
Input: rectangles = [[0,0,2,2],[1,0,2,3],[1,0,3,1]]
Output: 6
Explanation: A total area of 6 is covered by all three rectangles, as illustrated in the picture.
From (1,1) to (2,2), the green and red rectangles overlap.
From (1,0) to (2,3), all three rectangles overlap.
Example 2:
Input: rectangles = [[0,0,1000000000,1000000000]]
Output: 49
Explanation: The answer is 1018 modulo (109 + 7), which is 49.
Constraints:
1 <= rectangles.length <= 200rectanges[i].length == 40 <= xi1, yi1, xi2, yi2 <= 109xi1 <= xi2yi1 <= yi2- All rectangles have non zero area.
Code and Explanation
- Coordinate-compress all unique x and y coordinates to form a grid of atomic cells.
- For each rectangle, mark every grid cell it fully covers in a set (deduplication handles overlaps).
- Sum the area of all covered cells.
- Correct but slow — O(R · X · Y) where R = number of rectangles and X, Y = compressed grid sizes. Works for small inputs only.
Time: O(R · X · Y) | Space: O(X · Y)
- Sweep line on y: each rectangle adds a +1 event at its bottom edge and a −1 event at its top edge.
- Maintain a segment tree with lazy propagation over compressed x-coordinates. Each node tracks a
covercount (how many active rectangles span it) and thelengthof x-interval currently covered. - Between consecutive y-events, the total covered x-length (
length[1]) is constant; multiply by the y-strip height and accumulate area. cover > 0means the entire node range is covered; otherwise combine children. This is the classic lazy segment tree sweep-line template.
Time: O(n² log n) for n rectangles | Space: O(n)
Problem 7: Fancy Sequence (Leetcode:1622)#
Problem Statement
Write an API that generates fancy sequences using the append, addAll, and multAll operations.
Implement the Fancy class:
Fancy()Initializes the object with an empty sequence.void append(val)Appends an integervalto the end of the sequence.void addAll(inc)Increments all existing values in the sequence by an integerinc.void multAll(m)Multiplies all existing values in the sequence by an integerm.int getIndex(idx)Gets the current value at indexidx(0-indexed) of the sequence modulo109 + 7. If the index is greater or equal than the length of the sequence, return-1.
Example 1:
Input
["Fancy", "append", "addAll", "append", "multAll", "getIndex", "addAll", "append", "multAll", "getIndex", "getIndex", "getIndex"]
[[], [2], [3], [7], [2], [0], [3], [10], [2], [0], [1], [2]]
Output
[null, null, null, null, null, 10, null, null, null, 26, 34, 20]Explanation
Fancy fancy = new Fancy();
fancy.append(2); // fancy sequence: [2]
fancy.addAll(3); // fancy sequence: [2+3] -> [5]
fancy.append(7); // fancy sequence: [5, 7]
fancy.multAll(2); // fancy sequence: [52, 72] -> [10, 14]
fancy.getIndex(0); // return 10
fancy.addAll(3); // fancy sequence: [10+3, 14+3] -> [13, 17]
fancy.append(10); // fancy sequence: [13, 17, 10]
fancy.multAll(2); // fancy sequence: [132, 172, 10*2] -> [26, 34, 20]
fancy.getIndex(0); // return 26
fancy.getIndex(1); // return 34
fancy.getIndex(2); // return 20
Constraints:
1 <= val, inc, m <= 1000 <= idx <= 105- At most
105calls total will be made toappend,addAll,multAll, andgetIndex.
Code and Explanation
- Store the sequence literally and apply
addAll/multAllto every element immediately. appendandgetIndexare O(1); each bulk operation costs O(n) where n is the current sequence length.- With up to 10⁵ total calls this can degrade to O(n²) overall — correct but too slow at scale.
Time: append/getIndex O(1), addAll/multAll O(n) | Space: O(n)
- Defer bulk operations using lazy propagation tags: a global multiplier
lazy_multand offsetlazy_addapplied to all existing elements. - Each stored value is normalized at append time so that
stored[i] * lazy_mult + lazy_addgives the true current value. addAllbumps the lazy offset;multAllscales both the offset and multiplier — O(1) each, no array scan.getIndexapplies the pending affine transform to one element — the same lazy-tag idea used in segment trees for range add/multiply updates.
Time: all operations O(1) | Space: O(n)
