A clear explanation of Count of Smaller Numbers After Self using coordinate compression and a Fenwick Tree.
Problem Restatement
We are given an integer array nums.
For each index i, count how many numbers to the right of nums[i] are smaller than nums[i].
Return an array counts, where:
counts[i] = number of smaller elements to the right of nums[i]For example, in:
nums = [5, 2, 6, 1]the answer is:
[2, 1, 1, 0]because:
| Index | Value | Smaller values to the right | Count |
|---|---|---|---|
0 | 5 | 2, 1 | 2 |
1 | 2 | 1 | 1 |
2 | 6 | 1 | 1 |
3 | 1 | none | 0 |
The official constraints include 1 <= nums.length <= 10^5 and -10^4 <= nums[i] <= 10^4.
Input and Output
| Item | Meaning |
|---|---|
| Input | Integer array nums |
| Output | Integer array counts |
counts[i] | Number of elements smaller than nums[i] to its right |
| Important detail | Equal numbers are not smaller |
| Target | Efficient solution for large n |
Function shape:
def countSmaller(nums: list[int]) -> list[int]:
...Examples
Example 1:
nums = [5, 2, 6, 1]Output:
[2, 1, 1, 0]Example 2:
nums = [-1]Output:
[0]There is no number to the right.
Example 3:
nums = [-1, -1]Output:
[0, 0]The second -1 is equal to the first -1, not smaller.
First Thought: Compare Every Pair
The simplest method is:
- For every index
i. - Scan every index
j > i. - Count values where
nums[j] < nums[i].
Code idea:
for i in range(n):
for j in range(i + 1, n):
if nums[j] < nums[i]:
counts[i] += 1This is correct.
But it costs:
O(n^2)With n up to 100000, this is too slow.
Key Insight
Process the array from right to left.
When we stand at index i, all numbers to the right have already been seen.
So the problem becomes:
Among the seen numbers, how many are smaller than
nums[i]?
We need a data structure that supports:
| Operation | Meaning |
|---|---|
| Add one number | Mark that this value has appeared on the right |
| Count smaller numbers | Count how many seen values are less than current value |
A Fenwick Tree can do this efficiently if values are mapped into compact integer ranks.
Coordinate Compression
Fenwick Tree uses positive indices.
But nums may contain negative values.
So we compress values into ranks.
For example:
nums = [5, 2, 6, 1]Sorted unique values:
[1, 2, 5, 6]Ranks:
| Value | Rank |
|---|---|
1 | 1 |
2 | 2 |
5 | 3 |
6 | 4 |
Now “values smaller than 5” means “ranks less than 3”.
That can be queried as a prefix sum.
Fenwick Tree Meaning
The Fenwick Tree stores frequencies.
If we have already seen values on the right, then:
tree rank r = how many times value with rank r has appearedTo count numbers smaller than current value:
rank = value_to_rank[nums[i]]
answer[i] = tree.query(rank - 1)We query rank - 1 because equal values are not smaller.
Then we add the current value into the tree:
tree.add(rank, 1)Algorithm
- Sort all unique values in
nums. - Map each value to rank starting from
1. - Create a Fenwick Tree.
- Create result array
counts. - Iterate from right to left:
- Get rank of
nums[i]. - Query how many seen numbers have smaller rank.
- Store that count in
counts[i]. - Add current rank to the Fenwick Tree.
- Get rank of
- Return
counts.
Correctness
When the algorithm processes index i, it has already inserted exactly the elements to the right of i into the Fenwick Tree.
The Fenwick Tree stores frequencies by rank. Since coordinate compression preserves order, a value is smaller than nums[i] exactly when its rank is less than rank(nums[i]).
The query:
tree.query(rank - 1)returns the total frequency of all seen values with smaller rank. These are exactly the elements to the right of i that are smaller than nums[i].
After storing this count, the algorithm inserts nums[i] into the tree, so it becomes available for indices further left.
Therefore, every counts[i] is computed correctly.
Complexity
Let n = len(nums).
| Metric | Value | Why |
|---|---|---|
| Time | O(n log n) | Sorting unique values plus one Fenwick query and update per element |
| Space | O(n) | Rank map, Fenwick Tree, and answer array |
Implementation
class FenwickTree:
def __init__(self, size: int):
self.n = size
self.tree = [0] * (size + 1)
def add(self, index: int, delta: int) -> None:
while index <= self.n:
self.tree[index] += delta
index += index & -index
def query(self, index: int) -> int:
total = 0
while index > 0:
total += self.tree[index]
index -= index & -index
return total
class Solution:
def countSmaller(self, nums: list[int]) -> list[int]:
sorted_values = sorted(set(nums))
value_to_rank = {
value: rank
for rank, value in enumerate(sorted_values, start=1)
}
bit = FenwickTree(len(sorted_values))
counts = [0] * len(nums)
for i in range(len(nums) - 1, -1, -1):
rank = value_to_rank[nums[i]]
counts[i] = bit.query(rank - 1)
bit.add(rank, 1)
return countsCode Explanation
First, collect sorted unique values:
sorted_values = sorted(set(nums))Then map each value to a compact rank:
value_to_rank = {
value: rank
for rank, value in enumerate(sorted_values, start=1)
}Ranks start at 1 because Fenwick Tree uses 1-based indexing.
Create the tree and answer array:
bit = FenwickTree(len(sorted_values))
counts = [0] * len(nums)Now scan from right to left:
for i in range(len(nums) - 1, -1, -1):Get the current value’s rank:
rank = value_to_rank[nums[i]]Count seen values with smaller rank:
counts[i] = bit.query(rank - 1)Then insert the current number into the seen set:
bit.add(rank, 1)The Fenwick Tree methods are standard.
add updates all frequency buckets affected by one rank.
index += index & -indexquery sums frequencies from rank 1 through index.
index -= index & -indexTesting
def run_tests():
s = Solution()
assert s.countSmaller([5, 2, 6, 1]) == [2, 1, 1, 0]
assert s.countSmaller([-1]) == [0]
assert s.countSmaller([-1, -1]) == [0, 0]
assert s.countSmaller([1, 2, 3, 4]) == [0, 0, 0, 0]
assert s.countSmaller([4, 3, 2, 1]) == [3, 2, 1, 0]
assert s.countSmaller([2, 0, 1, 2, 1]) == [3, 0, 0, 1, 0]
assert s.countSmaller([0, -1, -1, 2]) == [2, 0, 0, 0]
print("all tests passed")
run_tests()Test meaning:
| Test | Why |
|---|---|
[5, 2, 6, 1] | Standard example |
[-1] | Single element |
[-1, -1] | Equal values are not smaller |
| Increasing array | All counts are zero |
| Decreasing array | Every later value is smaller |
| Duplicates | Confirms strict smaller logic |
| Negative values | Confirms coordinate compression |