315. Count of Smaller Numbers After Self
On LeetCode ->Problem¶
For each element, count how many strictly smaller elements appear to its right.
Key trick¶
Scan from right to left while a Fenwick tree stores frequencies by compressed value rank; the prefix sum before the current rank gives the number of strictly smaller values already seen.
Trap¶
- Counting equal values as smaller by querying through the current rank instead of stopping before it.
- Using values directly as indices without handling negatives or a large value range.
- Updating all larger keys in a map, which can degrade to \(O(n^2)\).
Why is it interesting?¶
It transforms a quadratic comparison problem into dynamic prefix-frequency queries, combining coordinate compression with a Fenwick tree.
Python solution¶
class Solution:
def countSmaller(self, nums: list[int]) -> list[int]:
# Compress values into 1-based ranks so negatives and gaps do not matter.
rank = {x: i + 1 for i, x in enumerate(sorted(set(nums)))}
tree = [0] * (len(rank) + 1)
ans = [0] * len(nums)
for i in range(len(nums) - 1, -1, -1):
idx = rank[nums[i]]
# Query ranks strictly below idx.
j = idx - 1
while j > 0:
ans[i] += tree[j]
j -= j & -j
# Add the current value to the frequencies.
j = idx
while j < len(tree):
tree[j] += 1
j += j & -j
return ans
- Time: \(O(n \log n)\)
- Space: \(O(n)\)
Comment on my solution¶
- The right-to-left scan is the correct starting point because all processed elements are exactly those to the current element's right.
- A plain map cannot efficiently count all keys below a value; it needs an ordered structure such as a Fenwick tree.
- Modeling every smaller pair as a graph edge requires up to \(O(n^2)\) edges.
- The count is not generally a longest-path length, and topological sorting does not provide the required per-index counts.
- The submitted method is incomplete because it does not compute or return a result.
# Ideas
# - Brute force is O(n^2)
# - As I've just learnt about Khan's topological order and this
# problem is in the section trees and graphs
# - maybe we can see nums as a directed graph where edges are
# from later and smaller node
# - and the indegree of each node i is counts[i]
# - this is also the length of the longest path to nums[i]
# - maybe scan right-to-left
# - keep a map of number -> how many inferior to it so far
class Solution:
def countSmaller(self, nums: list[int]) -> list[int]:
# [5,2,6,1] -> [2,1,1,0]
# [-1] -> [0]
# [-1,-1] -> [0,0]
Extra¶
I didn't know about Fenwick trees¶
Ok I couldn't have found this solution, first time I hear about Fenwick trees. Can you define them and explain me why they are the right tool for this problem?
Fenwick tree definition¶
A Fenwick tree, also called a Binary Indexed Tree, maintains frequencies or sums while supporting two operations efficiently:
- Add a value at one position.
- Compute the sum from the beginning through a position.
Both operations take \(O(\log n)\) instead of \(O(n)\).
How this problem becomes a prefix-sum problem¶
Process the array from right to left.
When examining a value x, the data structure contains exactly the values to its right. We need:
- A query: how many stored values are smaller than
x? - An update: insert
x.
If values were small positive indices, we could store their frequencies:
The number smaller than 5 is the prefix sum through value 4.
A dictionary can store each frequency, but answering “how many keys are below x?” requires scanning many keys. A Fenwick tree calculates that prefix sum in \(O(\log n)\).
Coordinate compression¶
The input can contain negatives and large gaps, so values cannot conveniently be used as array indices.
Sort the distinct values and assign each one a rank:
Only ordering matters:
Therefore, the number of values smaller than x is the frequency sum through:
Using the previous rank is important because equal values are not smaller.
What the Fenwick tree stores¶
For a normal frequency array, each position stores one frequency.
A Fenwick tree instead stores sums over carefully chosen ranges. For a 1-based index i, the range represented by tree[i] has length:
This expression extracts the lowest set bit of i.
For example:
tree[1] stores frequencies [1..1]
tree[2] stores frequencies [1..2]
tree[3] stores frequencies [3..3]
tree[4] stores frequencies [1..4]
tree[5] stores frequencies [5..5]
tree[6] stores frequencies [5..6]
tree[8] stores frequencies [1..8]
These overlapping ranges let us build any prefix from only \(O(\log n)\) stored sums.
Prefix-sum query¶
To get the sum through index i:
- Add
tree[i]. - Remove the lowest set bit from
i. - Repeat until
ibecomes zero.
For prefix 7, the visited indices are:
Their represented ranges are:
Point update¶
To add one occurrence at rank i:
- Update
tree[i]. - Move to the next range containing
i. - Repeat until leaving the tree.
Updating rank 5 in a tree of size 8 visits:
Those are exactly the stored ranges that include position 5.
Walkthrough¶
For:
Compression gives:
Scan from right to left:
| Current value | Rank | Query through | Smaller values seen | Result |
|---|---|---|---|---|
| 1 | 1 | 0 | none | 0 |
| 6 | 4 | 3 | 1 | 1 |
| 2 | 2 | 1 | 1 | 1 |
| 5 | 3 | 2 | 1, 2 | 2 |
So the answer is:
Why it is the right tool¶
The core pattern is:
That is precisely what a Fenwick tree is designed for.
- A plain list gives \(O(1)\) updates but \(O(n)\) prefix queries.
- Precomputed prefix sums give \(O(1)\) queries but \(O(n)\) after each update.
- A Fenwick tree gives \(O(\log n)\) for both.
- Coordinate compression turns arbitrary values into valid ordered indices.
The full algorithm therefore takes \(O(n \log n)\) time and \(O(n)\) space. Fenwick trees are not something you would normally derive from scratch during this problem; the interview skill is recognizing the dynamic prefix-sum pattern once you know the data structure.
A bit more about Fenwick and their application in the above algorithm¶
Thanks. But not much clearer to me.
1) I don't understand how the ranges are picked and why ~i & -i~ as range length makes sens. 2) I can't visualize the (left / right) tree branches. 3) I don't understand how the tree is built over time (while scanning right to left) 4) Can you go through the example ~[5, 2, 6, 1]~ step by step, specifically showing the tree being built.
The main missing idea is that a Fenwick tree is not a normal binary tree with left and right children. It is an array of overlapping range sums, with parent relationships implied by binary arithmetic.
1. How are the ranges chosen?¶
For each index \(i\), define:
This gives the largest power of two dividing \(i\).
i binary lowbit(i) stored range
1 0001 1 [1..1]
2 0010 2 [1..2]
3 0011 1 [3..3]
4 0100 4 [1..4]
5 0101 1 [5..5]
6 0110 2 [5..6]
7 0111 1 [7..7]
8 1000 8 [1..8]
The range stored at position \(i\) is:
For example, \(6\) in binary is:
Therefore, position \(6\) stores a range of length \(2\) ending at \(6\):
These ranges are not discovered from the input. They are a fixed design that breaks every prefix into a small number of power-of-two blocks.
For example:
The query obtains those blocks by repeatedly subtracting the low bit:
Each subtraction removes one set bit, so there are at most \(O(\log n)\) steps.
2. Where are the left and right branches?¶
There are no conventional left and right branches.
For a Fenwick tree of size \(4\), the stored ranges are:
The update-parent relationships can be visualized as:
Or as an implicit hierarchy:
This is not a binary search tree. The word “tree” refers to the implicit hierarchy among aggregated ranges.
When adding a value at position \(i\), the next aggregate containing it is found by:
For example, updating position \(1\) updates:
Those ranges all contain position \(1\):
Updating position \(3\) updates:
Those ranges both contain position \(3\):
3. How is the tree built over time?¶
It starts with zeros. There is no separate construction step.
While scanning right to left:
- Query how many previously seen values have a smaller rank.
- Add the current value's rank to the tree.
The tree therefore represents only the elements already encountered, which are exactly the elements to the current position's right.
For four ranks, the meaning of the tree array is always:
tree[1] = count at rank 1
tree[2] = count at ranks 1 through 2
tree[3] = count at rank 3
tree[4] = count at ranks 1 through 4
It is important that tree is not the raw frequency array. Its entries are aggregate counts.
4. Complete walkthrough of [5, 2, 6, 1]¶
First, compress the values into ranks:
Thus:
We scan in this order:
Initially:
The displayed tree omits its unused index \(0\).
Process 1¶
Its rank is \(1\).
To count values strictly smaller than rank \(1\), query through rank \(0\):
Now insert rank \(1\). The update path is:
Increment those tree positions:
Interpretation:
tree[1] = 1 value in rank [1..1]
tree[2] = 1 value in ranks [1..2]
tree[3] = 0 values in rank [3..3]
tree[4] = 1 value in ranks [1..4]
The partial answer is:
Process 6¶
Its rank is \(4\).
Strictly smaller ranks are \(1\) through \(3\), so query prefix \(3\).
The query path is:
Read:
The one smaller value is 1.
Now insert rank \(4\). Its update path is only:
The state becomes:
The partial answer is:
Process 2¶
Its rank is \(2\).
Strictly smaller ranks end at rank \(1\), so query prefix \(1\).
The smaller value is 1.
Now insert rank \(2\). The update path is:
The state becomes:
Notice:
It represents ranks \(1\) through \(2\), containing 1 and 2.
The partial answer is:
Process 5¶
Its rank is \(3\).
Strictly smaller ranks are \(1\) through \(2\), so query prefix \(2\).
The two smaller values are 1 and 2.
Now insert rank \(3\). The update path is:
The final state is:
The final answer is:
The entire execution can be summarized as:
value rank queried prefix query result update path tree after update
1 1 [none] 0 1, 2, 4 [1, 1, 0, 1]
6 4 [1..3] 1 4 [1, 1, 0, 2]
2 2 [1..1] 1 2, 4 [1, 2, 0, 3]
5 3 [1..2] 2 3, 4 [1, 2, 1, 4]
The essential distinction is:
Raw frequencies tell us how many times each rank occurred.
The Fenwick tree stores overlapping sums of those frequencies,
allowing prefix totals to be read and updated in O(log n).
how the answer is built¶
In the "Complete walkthrough of
[5, 2, 6, 1]" section, one thing is not clear to me:How
ansarray is built? We build the raw rank freqencies tree, and then Fenwick tree which end up being[1, 2, 1, 4], but how the answer[2, 1, 1, 0]is built?
The answer is not computed from the final Fenwick tree. Each answer entry is produced during the right-to-left scan and immediately stored at the current original index.
Start with:
We visit indices in reverse:
At each index:
- Query the Fenwick tree for the number of smaller values already seen.
- Store that query result in
ans[i]. - Insert the current value into the Fenwick tree.
Index 3, value 1¶
The tree contains no values yet because nothing is to the right of 1.
Then insert 1 into the tree.
Index 2, value 6¶
The tree currently represents:
There is one smaller value:
Then insert 6.
Index 1, value 2¶
The tree currently represents:
Only 1 is smaller than 2:
Then insert 2.
Index 0, value 5¶
The tree currently represents:
The values 2 and 1 are smaller than 5:
Then insert 5.
Combined view¶
i nums[i] values already in tree query result ans after query
3 1 [] 0 [0, 0, 0, 0]
2 6 [1] 1 [0, 0, 1, 0]
1 2 [1, 6] 1 [0, 1, 1, 0]
0 5 [1, 2, 6] 2 [2, 1, 1, 0]
The corresponding code structure is:
ans = [0] * len(nums)
for i in range(len(nums) - 1, -1, -1):
idx = rank[nums[i]]
# The tree currently contains only elements to index i's right.
ans[i] = query(idx - 1)
# Make nums[i] available when processing earlier indices.
add(idx)
The final Fenwick tree:
only says that all four ranks have now been inserted. It does not directly contain:
That answer comes from the four prefix queries made at different moments during the scan.
Pattern to solve problems with Fenwick trees¶
Now that you know what I was missing and misunderstanding, explain the whole algorithm again but in a concise way such that I can apply it to other problem.
The reusable pattern¶
Use a Fenwick tree when a problem repeatedly asks you to:
- Insert or update a value.
- Count or sum everything below some value.
- Preserve the ordering of values.
For this problem, scanning right to left turns:
Count smaller elements to the right.
into:
Among the values already inserted, how many are smaller than the current value?
1. Establish the scan invariant¶
Scan from right to left.
Before processing index \(i\), the Fenwick tree contains exactly:
Therefore, a query at index \(i\) counts only elements to its right.
The order is:
- Query the tree.
- Store the result in
ans[i]. - Insert
nums[i]into the tree.
The answer is built from query results over time, not from the final tree.
2. Compress values into ranks¶
Fenwick tree indices must be small positive integers, but input values may be negative, duplicated, or widely separated.
Map each distinct value to its sorted rank:
Ranks preserve comparisons:
3. Treat the tree as a frequency structure¶
In this problem, inserting rank \(r\) means increasing its frequency by one.
The Fenwick tree supports:
add(r, 1): insert one occurrence of rank \(r\).query(r): count inserted values with ranks from \(1\) through \(r\).
To count values strictly smaller than rank \(r\), use:
Using \(r-1\) excludes duplicates of the current value.
4. Fenwick tree mechanics¶
Each entry stores the sum of a fixed range ending at that index:
where:
There are no normal left and right branches. Binary arithmetic determines which aggregate ranges to visit.
def query(i):
total = 0
while i > 0:
total += tree[i]
i -= i & -i
return total
def add(i, delta):
while i < len(tree):
tree[i] += delta
i += i & -i
- A query moves toward smaller prefix blocks.
- An update moves toward larger ranges containing the updated position.
- Both take \(O(\log n)\).
5. Apply it to the example¶
Scan right to left:
i value rank inserted values query(rank - 1) ans[i]
3 1 1 [] 0 0
2 6 4 [1] 1 1
1 2 2 [1, 6] 1 1
0 5 3 [1, 2, 6] 2 2
This produces:
Complete implementation¶
class Solution:
def countSmaller(self, nums: list[int]) -> list[int]:
ranks = {x: i + 1 for i, x in enumerate(sorted(set(nums)))}
tree = [0] * (len(ranks) + 1)
ans = [0] * len(nums)
def query(i: int) -> int:
total = 0
while i > 0:
total += tree[i]
i -= i & -i
return total
def add(i: int) -> None:
while i < len(tree):
tree[i] += 1
i += i & -i
for i in range(len(nums) - 1, -1, -1):
rank = ranks[nums[i]]
# The tree contains exactly the elements to index i's right.
ans[i] = query(rank - 1)
add(rank)
return ans
- Time: \(O(n \log n)\)
- Space: \(O(n)\)
How to reuse the pattern¶
After compressing values to ranks, a Fenwick frequency tree can answer:
- Values less than \(x\):
- Values less than or equal to \(x\):
- Values greater than \(x\):
- Values greater than or equal to \(x\):
The general recipe is:
- Choose a scan direction so the tree represents the relevant past or future.
- Compress values if necessary.
- Convert the required comparison into a prefix query.
- Save the query result for the current position.
- Update the tree with the current value.