Segment Tree & Fenwick Tree
Prefix sums answer range questions instantly, until the array changes. Fenwick trees and segment trees keep range answers correct under updates in O(log n) each, and they also power "how many smaller values have I seen?" counting.
1. What is this pattern for?
Three related tools:
- Fenwick tree (binary indexed tree). Point update + prefix sum, both
O(log n), in about ten lines. Also counts "how many values ≤ x seen so far" over compressed values. - Segment tree. A binary tree over ranges; each node stores a summary (sum, max, min, or a custom merge like longest run). Point/range updates and range queries in
O(log n), plus "leftmost index with value ≥ x" by descending. - Sparse table. No updates, but range min/max in
O(1)afterO(n log n)preprocessing.
2. How it works (bisected)
- Name the query (sum, max, min, count of values in a range, a custom summary) and the update (point set, point add, none).
- Pick the structure: sums/counts with point updates → Fenwick; max/min or custom merges → segment tree; no updates and min/max → sparse table.
- Index it by position, or by value rank (coordinate compression) when you count values.
- Scan in the right order (often left-to-right or right-to-left) so the structure holds exactly the elements the query should see.
3. Seeing a problem: how to spot Segment / Fenwick Tree
Trigger words
- "update" and "sum/min/max of range" in the same problem (a design class)
- "count of smaller numbers after self", "reverse pairs", "inversions"
- "number of subarrays with sum in [lower, upper]"
- "after each query, the longest …" with single-element changes
- "leftmost position with capacity ≥ x" with removals
Shape of the input
- An array plus up to 10⁵ mixed updates and queries.
- Pair counting with an order condition (i < j and a comparison of values).
- Brute-force smell: rescanning a range per query, or O(n²) pair counting.
4. When to use it, and when not to
| Use a Fenwick / segment tree when… | Prefer something else when… |
|---|---|
| Range queries are mixed with updates | The array never changes: prefix sums (sums) or a sparse table (min/max) |
| Counting earlier elements smaller/greater than the current one | Only the k-th largest of a stream: a heap |
| You need max/min or a custom mergeable summary per range | All updates come before all queries: a difference array |
| "Leftmost index with value ≥ x" under removals | Values only increase and the query is a single global extreme: a heap |
Cost: Fenwick and segment tree are O(log n) per operation with O(n) space (segment tree ≈ 2–4n). A sparse table is O(1) per query, O(n log n) to build.
5. The Python template (memorize this)
# A) Fenwick tree: point add, prefix sum (1-indexed inside)
class Fenwick:
def __init__(self, n):
self.n = n
self.tree = [0] * (n + 1)
def add(self, i, delta): # position i is 1-based
while i <= self.n:
self.tree[i] += delta
i += i & -i # next block that covers i
def prefix(self, i): # sum of positions 1..i
total = 0
while i > 0:
total += self.tree[i]
i -= i & -i # drop the lowest set bit
return total
# B) iterative segment tree: point set, range max on [lo, hi]
class MaxTree:
def __init__(self, n):
self.size = 1
while self.size < n:
self.size *= 2
self.t = [0] * (2 * self.size) # leaves at size .. 2*size - 1
def set(self, i, val):
i += self.size
self.t[i] = val
i //= 2
while i:
self.t[i] = max(self.t[2 * i], self.t[2 * i + 1])
i //= 2
def query(self, lo, hi):
res = 0
lo += self.size
hi += self.size + 1
while lo < hi:
if lo & 1:
res = max(res, self.t[lo]); lo += 1
if hi & 1:
hi -= 1; res = max(res, self.t[hi])
lo //= 2; hi //= 2
return res
What the code is doing, line by line:
i & -iis the lowest set bit ofi: the size of the block thattree[i]covers (positionsi − (i & −i) + 1 … i).addclimbs to every block containing positioni;prefixdescends through disjoint blocks that tile1..i. Both visit at mostlog nentries.- A range sum is
prefix(r) − prefix(l − 1). - The segment tree stores leaves at
size + i; nodek's children are2kand2k + 1, so updating walksi //= 2to the root. - The query walks both ends upward, taking a node whenever it hangs outside the remaining range (
loodd orhiodd).
Dry run of the Fenwick tree on [5, 2, 9, 1] (positions 1–4):
| operation | indices touched | tree[1..4] after |
|---|---|---|
| add(1, 5) | 1, 2, 4 | 5, 5, 0, 5 |
| add(2, 2) | 2, 4 | 5, 7, 0, 7 |
| add(3, 9) | 3, 4 | 5, 7, 9, 16 |
| add(4, 1) | 4 | 5, 7, 9, 17 |
| prefix(3) | 3, 2 → 9 + 7 | = 16 (5 + 2 + 9) |
6. How to remember it forever
i += i & -i; prefix descends with i -= i & -i. Segment tree: leaves at size + i, parents at i // 2.How this hook works: what “Up by the low bit, down by the low bit” means
What the slogan means, phrase by phrase
- “the low bit”
i & -i, the lowest set bit ofi. In a Fenwick tree it is the size of the block thattree[i]covers:tree[6]covers 2 positions (5–6),tree[8]covers 8 (1–8).- “Up by the low bit”
addclimbs:i += i & -ireaches the next bigger block that also contains positioni, and every block containing it gets updated.- “down by the low bit”
prefixdescends:i -= i & -ijumps to the block just before the current one, adding non-overlapping blocks until 0.- “Segment tree: leaves at size + i, parents at i // 2”
- The array version of a segment tree: children of node
jare2jand2j + 1.
Why this picture fits the pattern: These trees exist for range questions with updates. Plain prefix sums answer fast but update slowly. Blocks of power-of-two size make both take O(log n), and the low bit is how you move between blocks.
The story → the code
| In the story | In the code |
|---|---|
| A block of days | tree[i] = sum of positions i - lowbit + 1 .. i |
| Fix an old entry | add(i, delta), climbing up |
| Total up to day i | prefix(i), climbing down |
| Total of days l..r | prefix(r) - prefix(l - 1): odometer minus odometer again |
Watch it on numbers
A Fenwick tree over 8 positions.
add(3, …): 3 → 3 + 1 = 4 → 4 + 4 = 8 → 16 is past the end, stop. Blocks 3, 4 and 8 contain position 3.prefix(7): taketree[7](position 7), go 7 − 1 = 6, taketree[6](5–6), go 6 − 2 = 4, taketree[4](1–4), go 4 − 4 = 0, stop.- Three blocks cover 1–7 exactly, with no overlap and no gap. Both operations are
O(log n).
The trap it saves you from
Using index 0: 0 & -0 is 0, so the loop never moves (an infinite loop). Fenwick positions are 1-based. Values up to 10⁹ must be compressed to ranks before they can be positions.
It still works when…
Count of Smaller Numbers After Self: walk from the right, ask prefix(rank - 1), then add(rank, 1). For range max, sums don't subtract, so use the segment tree.
Picture
A ruler with marks of different heights: tall marks (powers of two) summarise long stretches, short marks short ones.
Chant
"Sum with Fenwick, max with segments, frozen arrays with sparse tables."
Safety rule
"Compress before you count." Values up to 10⁹ need ranks before they index a tree.
7. LeetCode problems, solved step by step
Medium 307. Range Sum Query - Mutable Start here Google
Problem: Support update(index, val) and sumRange(left, right) on an array, both fast.
["NumArray", "sumRange", "update", "sumRange"]
[[[1, 3, 5]], [0, 2], [1, 2], [0, 2]][null, 9, null, 8]Constraints: 1 ≤ nums.length ≤ 3 * 10⁴ · -100 ≤ nums[i] ≤ 100 · 0 ≤ index < nums.length · -100 ≤ val ≤ 100 · 0 ≤ left ≤ right < nums.length · At most 3 * 10⁴ calls will be made to update and sumRange.
Hint 1
Prefix sums are fast to query but slow to update. Can you balance both?
Hint 2
Use a Fenwick tree or segment tree.
From brute force to the pattern: Recomputing sums per query is O(n); the Fenwick tree makes both operations O(log n).
Spot it: Prefix sums make queries O(1) but updates O(n). A Fenwick (binary indexed) tree stores partial sums over blocks sized by the lowest set bit, so both operations take O(log n).
Why not another pattern? A plain prefix-sum array needs an O(n) rebuild per update; recomputing sums per query is O(n).
Approach:
treeis 1-indexed;add(i, delta): whilei ≤ n:tree[i] += delta,i += i & −i.prefix(i): whilei > 0: sumtree[i],i −= i & −i.update: add the difference;sumRange(l, r) = prefix(r + 1) − prefix(l).
class NumArray:
def __init__(self, nums: List[int]):
self.n = len(nums)
self.nums = nums[:]
self.tree = [0] * (self.n + 1) # 1-indexed Fenwick tree
for i, x in enumerate(nums):
self._add(i + 1, x)
def _add(self, i, delta):
while i <= self.n:
self.tree[i] += delta
i += i & -i # next block that covers position i
def _prefix(self, i): # sum of the first i elements
total = 0
while i > 0:
total += self.tree[i]
i -= i & -i # drop the lowest set bit
return total
def update(self, index: int, val: int) -> None:
self._add(index + 1, val - self.nums[index])
self.nums[index] = val
def sumRange(self, left: int, right: int) -> int:
return self._prefix(right + 1) - self._prefix(left)
What the code is doing, line by line:
tree[i]holds the sum of thei & −ielements ending at positioni.- Updating walks up through every block containing the position; prefix queries walk down through disjoint blocks.
- Both walks visit at most
log nnodes.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- Single element.
- Updating to the same value.
- Range of the whole array.
Complexity: O(log n) per update and query, O(n log n) to build (or O(n) with a smarter build), O(n) space.
Hard 315. Count of Smaller Numbers After Self Start here Google Amazon
Problem: For each element, count how many elements to its right are smaller.
nums = [5,2,6,1][2,1,1,0]nums = [-1][0]Constraints: 1 ≤ nums.length ≤ 10⁵ · -10⁴ ≤ nums[i] ≤ 10⁴
Hint 1
Process from the right so that everything seen so far is to the right.
Hint 2
You need "how many seen values are smaller than x" quickly: a Fenwick tree over ranks.
From brute force to the pattern: All pairs: O(n²). Fenwick tree (or merge sort counting): O(n log n).
Spot it: Walk from the right, inserting values into a Fenwick tree indexed by rank (coordinate compression). Before inserting x, the prefix sum up to rank(x) − 1 is the number of smaller values already seen, i.e. to its right.
Why not another pattern? Checking all pairs is O(n²).
Approach:
- Rank values:
rank = {v: i + 1}over sorted distinct values. - From right to left:
counts[i] = prefix(rank[x] − 1); thenadd(rank[x], 1).
class Solution:
def countSmaller(self, nums: List[int]) -> List[int]:
rank = {v: i + 1 for i, v in enumerate(sorted(set(nums)))} # compress values to 1..m
m = len(rank)
tree = [0] * (m + 1)
counts = [0] * len(nums)
for i in range(len(nums) - 1, -1, -1): # right to left
r = rank[nums[i]] - 1
seen_smaller = 0
while r > 0:
seen_smaller += tree[r] # how many smaller values are to the right
r -= r & -r
counts[i] = seen_smaller
r = rank[nums[i]]
while r <= m:
tree[r] += 1 # record nums[i]
r += r & -r
return counts
What the code is doing, line by line:
- Ranks keep the tree size at the number of distinct values (values can be negative).
- Strictly smaller means querying up to
rank − 1.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- Duplicates (not counted as smaller).
- Negative values.
- Sorted ascending → all zeros.
Complexity: O(n log n) time, O(n) space.
Hard 2407. Longest Increasing Subsequence II Google
Problem: Find the longest strictly increasing subsequence where adjacent elements differ by at most k.
nums = [4,2,1,4,3,4,5,8,15], k = 35nums = [7,4,5,1,8,12,4,7], k = 54Constraints: 1 ≤ nums.length ≤ 10⁵ · 1 ≤ nums[i], k ≤ 10⁵
Hint 1
Let dp[v] be the longest valid subsequence ending with value v.
Hint 2
dp[v] depends on a range of values: use a structure with range max and point updates.
From brute force to the pattern: The pairwise DP is O(n²); a value-indexed segment tree is O(n log V).
Spot it: LIS ending at value v extends any subsequence ending at a value in [v − k, v − 1]. That's a range-max query plus a point update over values → a segment tree indexed by value.
Why not another pattern? The O(n²) LIS DP is too slow for n = 10⁵; patience sorting can't express the ≤ k gap rule.
Approach:
- Segment tree over values
1..max. - For each
x:cur = 1 + max(query(x − k, x − 1));update(x, cur). - Answer: the maximum
cur.
class Solution:
def lengthOfLIS(self, nums: List[int], k: int) -> int:
size = 1
while size <= max(nums):
size *= 2
tree = [0] * (2 * size) # iterative max segment tree over values 0..size-1
def query(lo, hi): # max of best[lo..hi]
res = 0
lo += size
hi += size + 1
while lo < hi:
if lo & 1:
res = max(res, tree[lo])
lo += 1
if hi & 1:
hi -= 1
res = max(res, tree[hi])
lo //= 2
hi //= 2
return res
def update(i, val):
i += size
tree[i] = max(tree[i], val)
i //= 2
while i:
tree[i] = max(tree[2 * i], tree[2 * i + 1])
i //= 2
best = 0
for x in nums:
cur = 1 + query(max(0, x - k), x - 1) # extend the best chain ending k or less below x
update(x, cur)
best = max(best, cur)
return best
What the code is doing, line by line:
treeleaves hold the best LIS length ending at each value; internal nodes hold maxima of their ranges.- The query covers exactly the values that may come right before
x(strictly smaller, gap ≤ k). - Processing in array order keeps the subsequence order correct.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- Equal values (strictly increasing: no chain).
k = 1(consecutive values only).- Single element.
Complexity: O(n log V) time, O(V) space (V = max(nums)).
8. Common mistakes
i & -i equal 0 and the update loop never ends. Shift positions by one.update(i, val) must add val − old and remember the new value.4n nodes; the iterative one needs 2 · size with size a power of two ≥ n.9. Self-check quiz
Point updates and range-sum queries, 10⁵ of each. Simplest structure?
Count of smaller numbers after self with a Fenwick tree: what's the scan?
Each query changes one character; report the longest run of equal characters. Which structure?
A fixed array, 10⁵ range-maximum queries, no updates. Fastest per query?
10. Practice list (in order)
| # | Problem | Level | Data structure | Twist |
|---|---|---|---|---|
| 303 | Range Sum Query – Immutable | Easy | Solved in 01 · Prefix Sum: Warm-up: the plain template | |
| 307 | Range Sum Query - Mutable | Medium | Fenwick tree: point update and prefix sum in O(log n) by walking i += i & −i / i −= i & −i | |
| 3479 | Fruits Into Baskets III | Medium | Max segment tree over baskets; descend to the leftmost leaf with capacity ≥ fruit, then zero it | |
| 1094 | Car Pooling | Medium | Solved in 08 · Overlapping Intervals: Sweep line / difference array with capacities | |
| 315 | Count of Smaller Numbers After Self | Hard | Scan right to left; a Fenwick tree over value ranks counts how many smaller values were seen | |
| 2407 | Longest Increasing Subsequence II | Hard | dp over values: best[v] = 1 + max(best[v−k..v−1]) via a max segment tree indexed by value | |
| 327 | Count of Range Sum | Hard | Range sum = P[j] − P[i]; count earlier prefixes in [P[j] − upper, P[j] − lower] with a Fenwick tree | |
| 493 | Reverse Pairs | Hard | For each j, count earlier values > 2·nums[j] with a Fenwick tree over compressed values | |
| 2213 | Longest Substring of One Repeating Character | Hard | Segment tree nodes store (longest prefix run, longest suffix run, best run); merge across the middle when the boundary chars match | |
| 3161 | Block Placement Queries | Hard | Store at each obstacle the gap to the previous obstacle in a max segment tree; query the max gap up to x | |
| 3501 | Maximize Active Section with Trade II | Hard | Gain = two neighbouring 0-blocks inside the query; inner pairs via a sparse table (range max), clipped edge blocks by hand | |
| 3691 | Maximum Total Subarray Value II | Hard | value(l, r) grows with r: heap of (value(l, n−1), l, r); pop k times, pushing (l, r−1); range max/min via sparse tables | |
| 218 | The Skyline Problem | Hard | Solved in 08 · Overlapping Intervals: Sweep events + max-heap of heights | |
| 3901 | Good Subsequence Queries | Hard | Segment tree of gcd over multiples of p (others count as 0); a proper subset with gcd p exists if a non-multiple exists or n ≥ 8 |
Worked solutions for the practice list
Problems already solved in section 7 link back up there, and rows marked "Solved in …" link to the page where that problem is worked through. Every other problem from the table is solved here.
Hard 327. Count of Range Sum Google Amazon
Problem: Count subarrays whose sum lies in [lower, upper].
nums = [-2,5,-1], lower = -2, upper = 23nums = [0], lower = 0, upper = 01Constraints: 1 ≤ nums.length ≤ 10⁵ · -2³¹ ≤ nums[i] ≤ 2³¹ - 1 · -10⁵ ≤ lower ≤ upper ≤ 10⁵ · The answer is guaranteed to fit in a 32-bit integer.
Hint 1
Rewrite the subarray sum with prefix sums.
Hint 2
For each prefix, how many earlier prefixes fall in a value window?
From brute force to the pattern: Every subarray: O(n²). Fenwick tree or merge sort: O(n log n).
Spot it: A subarray sum is P[j] − P[i] for prefix sums. For each j, count earlier prefixes in [P[j] − upper, P[j] − lower]: a range count over values seen so far → Fenwick tree with coordinate compression.
Why not another pattern? All subarrays: O(n²).
Approach:
- Prefix sums
P[0..n]; compress allPvalues. - For each
P[j]in order: count compressed prefixes with value in the window (twobisects on the sorted values, two prefix queries); then insertP[j].
from bisect import bisect_left, bisect_right
from itertools import accumulate
class Solution:
def countRangeSum(self, nums: List[int], lower: int, upper: int) -> int:
prefix = [0] + list(accumulate(nums))
values = sorted(set(prefix))
m = len(values)
tree = [0] * (m + 1)
def add(i):
while i <= m:
tree[i] += 1
i += i & -i
def count(i): # prefixes stored among the first i sorted values
total = 0
while i > 0:
total += tree[i]
i -= i & -i
return total
result = 0
for p in prefix:
lo = bisect_left(values, p - upper) # first value >= p - upper
hi = bisect_right(values, p - lower) # past the last value <= p - lower
result += count(hi) - count(lo) # earlier prefixes making a sum in range
add(bisect_left(values, p) + 1)
return result
What the code is doing, line by line:
lower ≤ P[j] − P[i] ≤ upper⇔P[j] − upper ≤ P[i] ≤ P[j] − lower.- Binary search maps the value window to a rank window; the Fenwick tree counts inserted prefixes in it.
- Inserting after counting ensures only
i < jare counted.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- Negative numbers.
lower == upper.- Single element.
Complexity: O(n log n) time, O(n) space.
Hard 493. Reverse Pairs Google Meta Amazon Microsoft
Problem: Count pairs i < j with nums[i] > 2 · nums[j].
nums = [1,3,2,3,1]2nums = [2,4,3,5,1]3Constraints: 1 ≤ nums.length ≤ 5 * 10⁴ · -2³¹ ≤ nums[i] ≤ 2³¹ - 1
Hint 1
For each element, count earlier elements greater than twice it.
Hint 2
Use a Fenwick tree over sorted values (or a modified merge sort).
From brute force to the pattern: All pairs: O(n²). Fenwick: O(n log n).
Spot it: Scan left to right; for each j ask "how many earlier values exceed 2 · nums[j]?" → Fenwick tree over ranks of all values.
Why not another pattern? All pairs: O(n²).
Approach:
- Sort distinct values.
- For each
x: count inserted values> 2x= inserted − (inserted values ≤ 2x). - Insert
x.
from bisect import bisect_left, bisect_right
class Solution:
def reversePairs(self, nums: List[int]) -> int:
values = sorted(set(nums))
m = len(values)
tree = [0] * (m + 1)
result = 0
for seen, x in enumerate(nums):
i = bisect_right(values, 2 * x) # values <= 2x occupy ranks 1..i
small = 0
while i > 0:
small += tree[i]
i -= i & -i
result += seen - small # earlier values greater than 2x
i = bisect_left(values, x) + 1
while i <= m:
tree[i] += 1
i += i & -i
return result
What the code is doing, line by line:
seenis the number of earlier elements; subtracting those ≤ 2x leaves those > 2x.- Ranks come from the array's own values;
bisect_rightfinds how many are ≤ 2x even though 2x itself may not be a value.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- Negative numbers (2x can be smaller than x).
- Large values (no overflow in Python).
- Duplicates.
Complexity: O(n log n) time, O(n) space.
Hard 2213. Longest Substring of One Repeating Character Google Amazon Microsoft
Problem: After each single-character update, return the length of the longest run of one repeated character.
s = "babacc", queryCharacters = "bcb", queryIndices = [1,3,3][3,3,4]s = "abyzz", queryCharacters = "aa", queryIndices = [2,1][2,3]Constraints: 1 ≤ s.length ≤ 10⁵ · s consists of lowercase English letters. · k == queryCharacters.length == queryIndices.length · 1 ≤ k ≤ 10⁵ · queryCharacters consists of lowercase English letters. · 0 ≤ queryIndices[i] < s.length
Hint 1
Each query changes one character. Which structure supports point updates and global queries?
Hint 2
What does a segment-tree node need to know to merge two halves' longest runs?
From brute force to the pattern: Rescanning after every query is O(nk); the segment tree is O(k log n).
Spot it: Each update changes one position; the answer is a global property. A segment tree where each node knows its best run, its run from the left edge and its run to the right edge can be merged in O(1), so each update costs O(log n).
Why not another pattern? Rescanning the string after each update is O(n · k).
Approach:
- Node for
[lo, hi]:pre,suf,best. - Merge
L = [lo, mid],R = [mid+1, hi]:join = s[mid] == s[mid+1];best = max(L.best, R.best, L.suf + R.pref if join). pre = L.pre + R.preifL.precovers all of L andjoin, elseL.pre; symmetric forsuf.- Update a leaf and re-merge up to the root; report
best[root].
class Solution:
def longestRepeating(self, s: str, queryCharacters: str, queryIndices: List[int]) -> List[int]:
n = len(s)
chars = list(s)
pre = [0] * (4 * n)
suf = [0] * (4 * n)
best = [0] * (4 * n)
def pull(node, lo, hi):
mid = (lo + hi) // 2
L, R = 2 * node, 2 * node + 1
join = chars[mid] == chars[mid + 1] # can runs continue across the middle?
best[node] = max(best[L], best[R], suf[L] + pre[R] if join else 0)
pre[node] = pre[L] + pre[R] if join and pre[L] == mid - lo + 1 else pre[L]
suf[node] = suf[R] + suf[L] if join and suf[R] == hi - mid else suf[R]
def build(node, lo, hi):
if lo == hi:
pre[node] = suf[node] = best[node] = 1
return
mid = (lo + hi) // 2
build(2 * node, lo, mid)
build(2 * node + 1, mid + 1, hi)
pull(node, lo, hi)
def update(node, lo, hi, i):
if lo == hi:
return # a single character is always a run of 1
mid = (lo + hi) // 2
if i <= mid:
update(2 * node, lo, mid, i)
else:
update(2 * node + 1, mid + 1, hi, i)
pull(node, lo, hi) # recombine on the way up
build(1, 0, n - 1)
result = []
for ch, i in zip(queryCharacters, queryIndices):
chars[i] = ch
update(1, 0, n - 1, i)
result.append(best[1])
return result
What the code is doing, line by line:
- A run either lies inside one half or crosses the middle; crossing runs are
L.suf + R.pref. - A node's prefix run extends into the right half only if the whole left half is one run and the middle characters match.
- Only the
O(log n)nodes on the path from the updated leaf change.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- Updating to the same character.
- Length-1 string.
- Runs joining across the middle.
Complexity: O(n + k log n) time, O(n) space.
Hard 3161. Block Placement Queries Google
Problem: Insert obstacles on a line; for each query, can a block of size sz fit inside [0, x] without crossing an obstacle?
queries = [[1,2],[2,3,3],[2,3,1],[2,2,2]][false,true,true]Constraints: 1 ≤ queries.length ≤ 15 * 10⁴ · 2 ≤ queries[i].length ≤ 3 · 1 ≤ queries[i][0] ≤ 2 · 1 ≤ x, sz ≤ min(5 * 10⁴, 3 * queries.length) · The input is generated such that for queries of type 1, no obstacle exists at distance x when the query is asked. · The input is generated such that there is at least one query of type 2.
Hint 1
For a type-2 query, what's the only thing that matters about [0, x]?
Hint 2
Store each obstacle's gap to the previous obstacle and query the maximum with a segment tree.
From brute force to the pattern: Scanning obstacles per query is O(q²); a max segment tree over gaps makes queries O(log X).
Spot it: The answer is whether the largest free gap inside [0, x] is ≥ sz. Keep the obstacles sorted (with 0 as a virtual obstacle); store each obstacle's gap to its predecessor at its position in a max segment tree. A query is the max over obstacles ≤ x, plus the tail gap x − last.
Why not another pattern? Scanning the obstacles per query is O(n) each.
Approach:
obstacles = [0](sorted); segment tree over positions0..max x.- Insert
xbetweenpandq:gap[x] = x − p,gap[q] = q − x(ifqexists). - Query
(x, sz):p= last obstacle ≤ x; fits iffmax(tree max over [0, p], x − p) ≥ sz.
from bisect import bisect_right, insort
class Solution:
def getResults(self, queries: List[List[int]]) -> List[bool]:
top = max(q[1] for q in queries) + 1
size = 1
while size < top:
size *= 2
tree = [0] * (2 * size) # tree over positions: gap ending at an obstacle
def set_gap(i, val):
i += size
tree[i] = val
i //= 2
while i:
tree[i] = max(tree[2 * i], tree[2 * i + 1])
i //= 2
def max_upto(hi): # max gap stored at positions 0..hi
res = 0
lo, hi = size, hi + size + 1
while lo < hi:
if lo & 1:
res = max(res, tree[lo])
lo += 1
if hi & 1:
hi -= 1
res = max(res, tree[hi])
lo //= 2
hi //= 2
return res
obstacles = [0] # the origin acts as an obstacle
result = []
for q in queries:
x = q[1]
k = bisect_right(obstacles, x)
prev = obstacles[k - 1]
if q[0] == 1:
set_gap(x, x - prev) # new gap ending at x
if k < len(obstacles):
nxt = obstacles[k]
set_gap(nxt, nxt - x) # the next obstacle's gap shrinks
insort(obstacles, x)
else:
biggest = max(max_upto(prev), x - prev) # gaps fully inside [0, x], plus the tail
result.append(biggest >= q[2])
return result
What the code is doing, line by line:
- Every gap between consecutive obstacles is stored at its right end, so "gaps inside [0, x]" are those stored at positions ≤ the last obstacle before
x. - The tail from the last obstacle to
xis not stored and is added separately. - Blocks may touch obstacles, so a gap of exactly
szfits.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- No obstacles yet (the whole [0, x] is free).
- A block exactly equal to a gap.
- Obstacles inserted out of order.
Complexity: O(q log X) for the tree, plus O(q) per sorted-list insert (O(log) with a balanced tree).
Medium 3479. Fruits Into Baskets III Start here Google
Problem: Each fruit (in order) goes to the leftmost unused basket with capacity ≥ its quantity. Count fruits left unplaced.
fruits = [4,2,5], baskets = [3,5,4]1fruits = [3,6,1], baskets = [6,4,7]0Constraints: n == fruits.length == baskets.length · 1 ≤ n ≤ 10⁵ · 1 ≤ fruits[i], baskets[i] ≤ 10⁹
Hint 1
You need the leftmost basket with capacity ≥ f, and baskets get used up.
Hint 2
A max segment tree can find it by descending from the root.
From brute force to the pattern: Linear scans per fruit: O(n²); tree descent: O(log n) per fruit.
Spot it: "Leftmost index with value ≥ x" with removals → a max segment tree: from the root, go left if the left child's max is enough, else right. Using a basket sets its leaf to 0.
Why not another pattern? Scanning baskets for each fruit is O(n²) with n = 10⁵.
Approach:
- Build a max tree over capacities (padded to a power of two).
- For each fruit: if the root max < fruit, unplaced. Otherwise descend to the leftmost fitting leaf and set it to 0.
class Solution:
def numOfUnplacedFruits(self, fruits: List[int], baskets: List[int]) -> int:
size = 1
while size < len(baskets):
size *= 2
tree = [0] * (2 * size)
tree[size:size + len(baskets)] = baskets # leaves
for i in range(size - 1, 0, -1):
tree[i] = max(tree[2 * i], tree[2 * i + 1])
unplaced = 0
for f in fruits:
if tree[1] < f:
unplaced += 1 # no basket is big enough
continue
i = 1
while i < size:
i = 2 * i if tree[2 * i] >= f else 2 * i + 1 # prefer the left half
tree[i] = 0 # this basket is used
i //= 2
while i:
tree[i] = max(tree[2 * i], tree[2 * i + 1])
i //= 2
return unplaced
What the code is doing, line by line:
- Each node holds the largest free capacity in its range.
- Going left whenever possible finds the leftmost fitting basket.
- Setting a used basket to 0 removes it from future searches.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- All fruits fit.
- A big fruit early blocks the only big basket.
- Equal capacities.
Complexity: O(n log n) time, O(n) space.
Hard 3501. Maximize Active Section with Trade II Google
Problem: Same trade as Range I, but each query applies it to a substring s[l..r] (padded with 1s). Return the total 1s after the best trade.
s = "01", queries = [[0,1]][1]s = "0100", queries = [[0,3],[0,2],[1,3],[2,3]][4,3,1,1]Constraints: 1 ≤ n == s.length ≤ 10⁵ · 1 ≤ queries.length ≤ 10⁵ · s[i] is either '0' or '1'. · queries[i] = [li, ri] · 0 ≤ li ≤ ri < n
Hint 1
Start from Range I: the gain is the sum of two neighbouring zero blocks.
Hint 2
For a query, which blocks can be cut short by the boundaries?
Hint 3
Answer the maximum over the untouched inner pairs with a range-max structure.
From brute force to the pattern: Recomputing blocks per query is O(nq); a sparse table makes each query O(log n) (for the binary searches).
Spot it: As in Range I, the best gain is the largest sum of two adjacent 0-blocks, now restricted to [l, r]. Inner pairs are fixed, so precompute pair[i] = len[i] + len[i+1] and answer range maxima with a sparse table. Only the first and last blocks can be clipped by the query, so pairs touching them are computed directly.
Why not another pattern? Recomputing blocks per query is O(n) each.
Approach:
- List 0-blocks
(start, end)with lengths;pair[i]and a sparse table for range max. - Query: blocks
a..boverlapping[l, r](binary search). If fewer than two, gain 0. - Clip
len_a,len_bto the query. Candidates: pairs(a, a+1)and(b−1, b)with clipped lengths, andmax(pair[a+1..b−2])from the table. - Answer: total 1s in
s+ best gain.
from bisect import bisect_left, bisect_right
class Solution:
def maxActiveSectionsAfterTrade(self, s: str, queries: List[List[int]]) -> List[int]:
ones = s.count("1")
starts, ends = [], [] # zero blocks
i = 0
while i < len(s):
if s[i] == "0":
j = i
while j + 1 < len(s) and s[j + 1] == "0":
j += 1
starts.append(i)
ends.append(j)
i = j + 1
else:
i += 1
length = [e - st + 1 for st, e in zip(starts, ends)]
pair = [length[t] + length[t + 1] for t in range(len(length) - 1)] # two neighbouring blocks
table = [pair] # sparse table: table[j][t] = max of pair[t .. t + 2^j - 1]
while len(table[-1]) > 1 and (1 << len(table)) <= len(pair):
prev, half = table[-1], 1 << (len(table) - 1)
table.append([max(prev[t], prev[t + half]) for t in range(len(prev) - half)])
def range_max(lo, hi): # max of pair[lo..hi], 0 if empty
if lo > hi:
return 0
j = (hi - lo + 1).bit_length() - 1
return max(table[j][lo], table[j][hi - (1 << j) + 1])
answer = []
for l, r in queries:
a = bisect_left(ends, l) # first block ending at or after l
b = bisect_right(starts, r) - 1 # last block starting at or before r
if b - a < 1:
answer.append(ones) # fewer than two zero blocks: no useful trade
continue
len_a = ends[a] - max(starts[a], l) + 1 # clipped by the query
len_b = min(ends[b], r) - starts[b] + 1
if b == a + 1:
gain = len_a + len_b
else:
gain = max(len_a + length[a + 1], length[b - 1] + len_b, range_max(a + 1, b - 2))
answer.append(ones + gain)
return answer
What the code is doing, line by line:
- Only the first and last overlapping zero blocks can be cut by the query edges; every block in between is complete.
pair[t]fora + 1 ≤ t ≤ b − 2involves only complete blocks, so the sparse table answers them inO(1).- The trade happens inside the substring, but the count is of 1s in the whole string, which is why
onesis global.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- Query covering no zeros.
- Query cutting both edge blocks.
- Exactly two blocks in the query.
Complexity: O(n log n + q log n) time, O(n log n) space.
Hard 3691. Maximum Total Subarray Value II Google
Problem: Choose k distinct subarrays maximising the sum of their (max − min) values.
nums = [1,3,2], k = 24nums = [4,2,5,1], k = 312Constraints: 1 ≤ n == nums.length ≤ 5 * 10⁴ · 0 ≤ nums[i] ≤ 10⁹ · 1 ≤ k ≤ min(10⁵, n * (n + 1) / 2)
Hint 1
For a fixed left end, how does the value change as the right end grows?
Hint 2
Merge the per-left-end sorted lists with a heap; get range max/min in O(1) with sparse tables.
From brute force to the pattern: All subarrays sorted: O(n² log n). Heap merge: O((n + k) log n).
Spot it: Take the k largest subarray values. For a fixed left end, the value never decreases as r grows, so the largest is at r = n − 1. A max-heap merges these sorted lists (k-way merge), and sparse tables give each range max/min in O(1).
Why not another pattern? Listing all n²/2 subarray values is too many for n = 5·10⁴.
Approach:
- Sparse tables for range max and min.
- Push
(value(l, n − 1), l, n − 1)for everyl. - Pop
ktimes; add the value; ifr > l, push(value(l, r − 1), l, r − 1).
import heapq
class Solution:
def maxTotalValue(self, nums: List[int], k: int) -> int:
n = len(nums)
hi_t, lo_t = [nums[:]], [nums[:]] # sparse tables for range max / min
j = 1
while (1 << j) <= n:
half = 1 << (j - 1)
hi_t.append([max(hi_t[-1][i], hi_t[-1][i + half]) for i in range(n - (1 << j) + 1)])
lo_t.append([min(lo_t[-1][i], lo_t[-1][i + half]) for i in range(n - (1 << j) + 1)])
j += 1
def value(l, r):
j = (r - l + 1).bit_length() - 1
top = max(hi_t[j][l], hi_t[j][r - (1 << j) + 1]) # two overlapping blocks cover [l, r]
low = min(lo_t[j][l], lo_t[j][r - (1 << j) + 1])
return top - low
heap = [(-value(l, n - 1), l, n - 1) for l in range(n)] # best subarray for each left end
heapq.heapify(heap)
total = 0
for _ in range(k):
neg, l, r = heapq.heappop(heap)
total -= neg
if r > l:
heapq.heappush(heap, (-value(l, r - 1), l, r - 1)) # next best with the same left end
return total
What the code is doing, line by line:
- Shrinking a subarray can only lower its max and raise its min, so for each
lthe values are sorted byrdescending. - The heap always holds the next-best candidate of every left end: a k-way merge of sorted lists.
- A sparse table answers a range max/min with two overlapping power-of-two blocks.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- k equal to the number of subarrays.
- All equal values (every value 0).
- n = 1.
Complexity: O(n log n + k log n) time, O(n log n) space.
Hard 3901. Good Subsequence Queries Amazon
Problem: After each point update, does some non-empty subsequence of length < n have gcd exactly p? Count the queries where it does.
nums = [4,8,12,16], p = 2, queries = [[0,3],[2,6]]1nums = [4,5,7,8], p = 3, queries = [[0,6],[1,9],[2,3]]2Constraints: 2 ≤ n == nums.length ≤ 5 * 10⁴ · 1 ≤ nums[i] ≤ 5 * 10⁴ · 1 ≤ queries.length ≤ 5 * 10⁴ · queries[i] = [indi, vali] · 1 ≤ vali, p ≤ 5 * 10⁴ · 0 ≤ indi ≤ n - 1
Hint 1
Which elements can belong to a subsequence with gcd exactly p?
Hint 2
Keep the gcd of those elements under point updates: a segment tree.
Hint 3
When every element is a multiple of p, when does a proper subset still work?
From brute force to the pattern: Recomputing per query is O(n · q); the segment tree is O(log n) per query (plus a tiny check for n < 8).
Spot it: Only multiples of p can be in such a subsequence, and the best candidate is all of them: their gcd must be p. Keep that gcd under updates with a segment tree (non-multiples stored as 0, the gcd identity). If every element is a multiple, a proper subset with gcd p still exists when n ≥ 8 (numbers ≤ 5·10⁴ have at most 6 prime factors); for small n check each removal directly.
Why not another pattern? Recomputing gcds over the array per query is O(n · q).
Approach:
- Leaf
iholdsnums[i]ifpdivides it, else 0; internal nodes hold the gcd. - Per query: update the leaf and the count of multiples. Let
G= root gcd. - Good if
G == pand (some element isn't a multiple, orn ≥ 8, or removing one element keeps gcdp).
from math import gcd
class Solution:
def countGoodSubseq(self, nums: List[int], p: int, queries: List[List[int]]) -> int:
n = len(nums)
size = 1
while size < n:
size *= 2
tree = [0] * (2 * size) # gcd of multiples of p; 0 is the identity
def put(i, value):
i += size
tree[i] = value if value % p == 0 else 0
i //= 2
while i:
tree[i] = gcd(tree[2 * i], tree[2 * i + 1])
i //= 2
multiples = 0
for i, v in enumerate(nums):
put(i, v)
multiples += v % p == 0
good = 0
for idx, val in queries:
multiples += (val % p == 0) - (nums[idx] % p == 0)
nums[idx] = val
put(idx, val)
if tree[1] != p:
continue # even all multiples together don't have gcd exactly p
if multiples < n or n >= 8:
good += 1 # all multiples form a proper subsequence (or a small proper subset exists)
continue
for skip in range(n): # tiny n: try leaving one element out
g = 0
for j in range(n):
if j != skip:
g = gcd(g, nums[j])
if g == p:
good += 1
break
return good
What the code is doing, line by line:
- A subsequence with gcd exactly
puses only multiples ofp; adding more multiples can only lower the gcd toward their total gcd, so the total gcd must bep. - If some element isn't a multiple, "all multiples" is already shorter than
n. - If all are multiples with gcd
p, the values divided byphave gcd 1; one element plus one counterexample per prime factor (at most 6) gives a gcd-1 subset of at most 7 elements, so n ≥ 8 always works.
Dry run: use the Step through the solution player under the code. It runs this exact code on Example 1 and shows every line with the variables at that moment.
Edge cases to test:
- No multiples of p.
- All elements are multiples (needs a proper subset).
- p itself in the array.
Complexity: O((n + q) log n · log V) time, O(n) space.