315. Count of Smaller Numbers After Self
Problem Description
You are given an integer array nums
. For each element at position i
in the array, you need to count how many elements to its right (positions i+1
to the end) are smaller than nums[i]
.
Return an array counts
where counts[i]
represents the number of elements smaller than nums[i]
that appear after position i
.
For example, if nums = [5, 2, 6, 1]
:
- For
nums[0] = 5
: elements to the right are[2, 6, 1]
, and2
and1
are smaller than5
, socounts[0] = 2
- For
nums[1] = 2
: elements to the right are[6, 1]
, and only1
is smaller than2
, socounts[1] = 1
- For
nums[2] = 6
: elements to the right are[1]
, and1
is smaller than6
, socounts[2] = 1
- For
nums[3] = 1
: no elements to the right, socounts[3] = 0
The result would be counts = [2, 1, 1, 0]
.
The solution uses a Binary Indexed Tree (Fenwick Tree) data structure combined with coordinate compression. The approach works by:
-
Coordinate Compression: First, all unique values in
nums
are sorted and mapped to indices starting from 1. This compression reduces the range of values we need to handle. -
Reverse Processing: The array is processed from right to left. For each element:
- Query the Binary Indexed Tree to count how many elements smaller than the current element have been seen so far
- Update the tree by adding the current element
-
Binary Indexed Tree Operations:
update(x, delta)
: Addsdelta
to positionx
in the treequery(x)
: Returns the sum of all elements from position 1 tox
lowbit(x)
: Helper function that returns the rightmost set bit, used for tree navigation
Since we process elements from right to left and build our answer array in reverse, the final step reverses the answer array to get the correct order.
Intuition
The naive approach would be to check every pair of indices (i, j)
where j > i
and count how many times nums[j] < nums[i]
. This would take O(n²)
time, which is inefficient for large arrays.
The key insight is to process the array from right to left. When we're at position i
, we've already seen all elements that come after it. If we can efficiently query "how many elements smaller than nums[i]
have we seen so far?", we solve the problem.
This naturally leads us to think about data structures that support:
- Efficient insertion of elements as we see them
- Efficient counting of elements less than a given value
A Binary Indexed Tree (Fenwick Tree) is perfect for this because it can handle both operations in O(log n)
time. However, there's a challenge: Binary Indexed Trees work with array indices, not arbitrary values.
This is where coordinate compression comes in. If nums = [5, 2, 6, 1]
, we notice that the actual values don't matter - only their relative ordering does. We can map these to smaller indices: 1 → 1
, 2 → 2
, 5 → 3
, 6 → 4
. This mapping preserves the relative order while giving us a compact range [1, 4]
to work with.
Walking through the process backwards:
- Start from the last element (nothing to its right, count = 0)
- Move to the second-last element, check how many smaller elements we've seen
- Continue this pattern, building up our "seen" elements in the Binary Indexed Tree
The Binary Indexed Tree acts like a frequency array where tree[x]
conceptually represents how many times we've seen value x
. When we query for values less than x
, we sum up frequencies from 1
to x-1
, giving us the count of smaller elements.
By processing right-to-left and using the tree to track what we've seen, we transform a quadratic problem into one that runs in O(n log n)
time.
Solution Approach
The implementation consists of two main components: the Binary Indexed Tree class and the main solution logic.
Binary Indexed Tree Implementation:
The BinaryIndexedTree
class maintains an array c
of size n+1
(1-indexed for easier calculations). The core operations are:
-
lowbit(x)
: Returnsx & -x
, which isolates the rightmost set bit. For example,lowbit(6) = 2
because6
in binary is110
, and the rightmost set bit represents2
. -
update(x, delta)
: Addsdelta
to positionx
and propagates the update upward in the tree structure. Starting from indexx
, it moves to the next responsible node by addinglowbit(x)
until it exceedsn
. -
query(x)
: Computes the prefix sum from index1
tox
. It accumulates values by moving downward in the tree, subtractinglowbit(x)
at each step until reaching0
.
Main Solution Logic:
-
Coordinate Compression:
alls = sorted(set(nums)) m = {v: i for i, v in enumerate(alls, 1)}
- Extract unique values from
nums
and sort them - Create a mapping where each unique value maps to an index starting from
1
- This compresses the value range to
[1, len(unique_values)]
- Extract unique values from
-
Initialize the Tree:
tree = BinaryIndexedTree(len(m))
Create a Binary Indexed Tree with size equal to the number of unique values.
-
Process Array in Reverse:
for v in nums[::-1]: x = m[v] tree.update(x, 1) ans.append(tree.query(x - 1))
- Iterate through
nums
from right to left - For each value
v
, get its compressed indexx
- Update the tree at position
x
(increment frequency by1
) - Query the tree for sum of positions
[1, x-1]
to count smaller elements - Append this count to the answer list
- Iterate through
-
Return Reversed Result:
return ans[::-1]
Since we processed the array backwards, reverse the answer to get the correct order.
Example Walkthrough:
For nums = [5, 2, 6, 1]
:
- Compression:
{1:1, 2:2, 5:3, 6:4}
- Process
1
: update position1
, query[1,0]
=0
- Process
6
: update position4
, query[1,3]
=1
(only1
is smaller) - Process
2
: update position2
, query[1,1]
=1
(only1
is smaller) - Process
5
: update position3
, query[1,2]
=2
(both1
and2
are smaller) - Reverse to get
[2, 1, 1, 0]
The time complexity is O(n log n)
for sorting and O(n log n)
for the tree operations, giving overall O(n log n)
. Space complexity is O(n)
for the tree and mapping structures.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with nums = [5, 2, 6, 1]
to see how the Binary Indexed Tree approach works.
Step 1: Coordinate Compression
- Extract unique values:
{5, 2, 6, 1}
- Sort them:
[1, 2, 5, 6]
- Create mapping:
{1:1, 2:2, 5:3, 6:4}
- This maps each value to a compressed index (1-indexed)
Step 2: Initialize Binary Indexed Tree
- Create a tree of size 4 (number of unique values)
- Initially, all frequencies are 0:
tree = [0, 0, 0, 0, 0]
(index 0 unused)
Step 3: Process Array from Right to Left
Processing nums[3] = 1
:
- Compressed index:
x = 1
- Query tree for positions [1, 0]: count = 0 (no elements smaller than 1)
- Update tree at position 1 (add 1)
- Tree state:
[0, 1, 1, 0, 1]
(after update propagation) - Answer so far:
[0]
Processing nums[2] = 6
:
- Compressed index:
x = 4
- Query tree for positions [1, 3]: count = 1 (element 1 is smaller)
- Update tree at position 4 (add 1)
- Tree state:
[0, 1, 1, 0, 2]
(after update propagation) - Answer so far:
[0, 1]
Processing nums[1] = 2
:
- Compressed index:
x = 2
- Query tree for positions [1, 1]: count = 1 (element 1 is smaller)
- Update tree at position 2 (add 1)
- Tree state:
[0, 1, 2, 0, 3]
(after update propagation) - Answer so far:
[0, 1, 1]
Processing nums[0] = 5
:
- Compressed index:
x = 3
- Query tree for positions [1, 2]: count = 2 (elements 1 and 2 are smaller)
- Update tree at position 3 (add 1)
- Tree state:
[0, 1, 2, 1, 4]
(after update propagation) - Answer so far:
[0, 1, 1, 2]
Step 4: Reverse the Answer
- We built the answer backwards:
[0, 1, 1, 2]
- Reverse to get final result:
[2, 1, 1, 0]
This matches our expected output where:
nums[0] = 5
has 2 smaller elements to its right (2 and 1)nums[1] = 2
has 1 smaller element to its right (1)nums[2] = 6
has 1 smaller element to its right (1)nums[3] = 1
has 0 smaller elements to its right
The Binary Indexed Tree efficiently tracks the frequency of each compressed value we've seen, allowing us to query "how many values less than X have we encountered?" in O(log n) time.
Solution Implementation
1from typing import List
2
3
4class BinaryIndexedTree:
5 """
6 Binary Indexed Tree (Fenwick Tree) for efficient prefix sum queries and updates.
7 Supports O(log n) updates and range sum queries.
8 """
9
10 def __init__(self, n: int) -> None:
11 """
12 Initialize a Binary Indexed Tree with size n.
13
14 Args:
15 n: The size of the tree (1-indexed)
16 """
17 self.size = n
18 self.tree = [0] * (n + 1) # 1-indexed array for BIT
19
20 @staticmethod
21 def lowbit(x: int) -> int:
22 """
23 Get the lowest set bit of x using bit manipulation.
24 This determines the range of responsibility for each node.
25
26 Args:
27 x: The input number
28
29 Returns:
30 The value of the lowest set bit
31 """
32 return x & -x
33
34 def update(self, index: int, delta: int) -> None:
35 """
36 Add delta to the element at position index.
37 Updates all affected nodes in the tree.
38
39 Args:
40 index: The position to update (1-indexed)
41 delta: The value to add
42 """
43 while index <= self.size:
44 self.tree[index] += delta
45 index += BinaryIndexedTree.lowbit(index)
46
47 def query(self, index: int) -> int:
48 """
49 Calculate the prefix sum from position 1 to index.
50
51 Args:
52 index: The end position of the prefix sum (1-indexed)
53
54 Returns:
55 The sum of elements from position 1 to index
56 """
57 prefix_sum = 0
58 while index > 0:
59 prefix_sum += self.tree[index]
60 index -= BinaryIndexedTree.lowbit(index)
61 return prefix_sum
62
63
64class Solution:
65 def countSmaller(self, nums: List[int]) -> List[int]:
66 """
67 Count the number of smaller elements to the right of each element.
68
69 Uses coordinate compression and a Binary Indexed Tree to efficiently
70 count inversions while processing the array from right to left.
71
72 Args:
73 nums: List of integers
74
75 Returns:
76 List where result[i] is the count of smaller elements after nums[i]
77 """
78 # Step 1: Coordinate compression - map values to ranks
79 # Sort unique values and create a mapping to compressed indices
80 unique_sorted_values = sorted(set(nums))
81 value_to_rank = {value: rank for rank, value in enumerate(unique_sorted_values, 1)}
82
83 # Step 2: Initialize Binary Indexed Tree with compressed range
84 fenwick_tree = BinaryIndexedTree(len(value_to_rank))
85
86 # Step 3: Process array from right to left
87 result = []
88 for value in reversed(nums):
89 # Get the compressed rank of current value
90 rank = value_to_rank[value]
91
92 # Update the tree to mark this value as seen
93 fenwick_tree.update(rank, 1)
94
95 # Query how many values smaller than current value we've seen
96 # (rank - 1) gives us all ranks strictly less than current rank
97 smaller_count = fenwick_tree.query(rank - 1)
98 result.append(smaller_count)
99
100 # Step 4: Reverse result since we processed array backwards
101 return result[::-1]
102
1class Solution {
2 public List<Integer> countSmaller(int[] nums) {
3 // Step 1: Collect all unique values from nums
4 Set<Integer> uniqueValues = new HashSet<>();
5 for (int num : nums) {
6 uniqueValues.add(num);
7 }
8
9 // Step 2: Sort unique values for coordinate compression
10 List<Integer> sortedUniqueValues = new ArrayList<>(uniqueValues);
11 sortedUniqueValues.sort(Comparator.comparingInt(a -> a));
12
13 // Step 3: Create mapping from original value to compressed index (1-indexed)
14 int uniqueCount = sortedUniqueValues.size();
15 Map<Integer, Integer> valueToIndex = new HashMap<>(uniqueCount);
16 for (int i = 0; i < uniqueCount; i++) {
17 valueToIndex.put(sortedUniqueValues.get(i), i + 1);
18 }
19
20 // Step 4: Use Binary Indexed Tree to count smaller elements
21 BinaryIndexedTree fenwickTree = new BinaryIndexedTree(uniqueCount);
22 LinkedList<Integer> result = new LinkedList<>();
23
24 // Process array from right to left
25 for (int i = nums.length - 1; i >= 0; i--) {
26 // Get compressed index for current number
27 int compressedIndex = valueToIndex.get(nums[i]);
28
29 // Add current number to the tree
30 fenwickTree.update(compressedIndex, 1);
31
32 // Query count of numbers smaller than current (index - 1)
33 result.addFirst(fenwickTree.query(compressedIndex - 1));
34 }
35
36 return result;
37 }
38}
39
40class BinaryIndexedTree {
41 private int size;
42 private int[] tree;
43
44 /**
45 * Initialize Binary Indexed Tree with given size
46 * @param size the maximum number of elements
47 */
48 public BinaryIndexedTree(int size) {
49 this.size = size;
50 this.tree = new int[size + 1]; // 1-indexed array
51 }
52
53 /**
54 * Update the value at index x by adding delta
55 * @param x the index to update (1-indexed)
56 * @param delta the value to add
57 */
58 public void update(int x, int delta) {
59 // Propagate the update to all affected nodes
60 while (x <= size) {
61 tree[x] += delta;
62 x += lowbit(x); // Move to next node that needs updating
63 }
64 }
65
66 /**
67 * Query the prefix sum from index 1 to x
68 * @param x the end index of the range (1-indexed)
69 * @return the sum of elements from 1 to x
70 */
71 public int query(int x) {
72 int sum = 0;
73 // Aggregate values from all relevant nodes
74 while (x > 0) {
75 sum += tree[x];
76 x -= lowbit(x); // Move to parent node
77 }
78 return sum;
79 }
80
81 /**
82 * Get the lowest set bit of x
83 * @param x the input number
84 * @return the value of the lowest set bit
85 */
86 public static int lowbit(int x) {
87 return x & -x;
88 }
89}
90
1class BinaryIndexedTree {
2public:
3 int size;
4 vector<int> tree;
5
6 // Initialize BIT with given size
7 BinaryIndexedTree(int n) : size(n), tree(n + 1, 0) {}
8
9 // Add delta to position x and propagate to all affected nodes
10 void update(int x, int delta) {
11 while (x <= size) {
12 tree[x] += delta;
13 x += lowbit(x); // Move to next node that includes this position
14 }
15 }
16
17 // Query prefix sum from index 1 to x
18 int query(int x) {
19 int sum = 0;
20 while (x > 0) {
21 sum += tree[x];
22 x -= lowbit(x); // Move to parent node
23 }
24 return sum;
25 }
26
27 // Get the lowest set bit of x (isolate rightmost 1-bit)
28 int lowbit(int x) {
29 return x & -x;
30 }
31};
32
33class Solution {
34public:
35 vector<int> countSmaller(vector<int>& nums) {
36 // Step 1: Coordinate compression - get unique values
37 unordered_set<int> uniqueNums(nums.begin(), nums.end());
38 vector<int> sortedUnique(uniqueNums.begin(), uniqueNums.end());
39 sort(sortedUnique.begin(), sortedUnique.end());
40
41 // Step 2: Map each unique value to its compressed index (1-indexed for BIT)
42 unordered_map<int, int> valueToIndex;
43 int uniqueCount = sortedUnique.size();
44 for (int i = 0; i < uniqueCount; ++i) {
45 valueToIndex[sortedUnique[i]] = i + 1; // 1-indexed for BIT
46 }
47
48 // Step 3: Initialize BIT with compressed range size
49 BinaryIndexedTree* bit = new BinaryIndexedTree(uniqueCount);
50
51 // Step 4: Process array from right to left
52 vector<int> result(nums.size());
53 for (int i = nums.size() - 1; i >= 0; --i) {
54 // Get compressed index of current number
55 int compressedIndex = valueToIndex[nums[i]];
56
57 // Update BIT: mark this number as seen
58 bit->update(compressedIndex, 1);
59
60 // Query how many numbers are smaller (indices 1 to compressedIndex-1)
61 result[i] = bit->query(compressedIndex - 1);
62 }
63
64 return result;
65 }
66};
67
1// Binary Indexed Tree (Fenwick Tree) for efficient range sum queries
2let size: number;
3let tree: number[];
4
5// Initialize BIT with given size
6function initBIT(n: number): void {
7 size = n;
8 tree = new Array(n + 1).fill(0);
9}
10
11// Get the lowest set bit of x (isolate rightmost 1-bit)
12function lowbit(x: number): number {
13 return x & -x;
14}
15
16// Add delta to position x and propagate to all affected nodes
17function update(x: number, delta: number): void {
18 while (x <= size) {
19 tree[x] += delta;
20 x += lowbit(x); // Move to next node that includes this position
21 }
22}
23
24// Query prefix sum from index 1 to x
25function query(x: number): number {
26 let sum = 0;
27 while (x > 0) {
28 sum += tree[x];
29 x -= lowbit(x); // Move to parent node
30 }
31 return sum;
32}
33
34// Main solution function to count smaller elements to the right
35function countSmaller(nums: number[]): number[] {
36 // Step 1: Coordinate compression - get unique values
37 const uniqueNums = new Set<number>(nums);
38 const sortedUnique = Array.from(uniqueNums).sort((a, b) => a - b);
39
40 // Step 2: Map each unique value to its compressed index (1-indexed for BIT)
41 const valueToIndex = new Map<number, number>();
42 const uniqueCount = sortedUnique.length;
43 for (let i = 0; i < uniqueCount; i++) {
44 valueToIndex.set(sortedUnique[i], i + 1); // 1-indexed for BIT
45 }
46
47 // Step 3: Initialize BIT with compressed range size
48 initBIT(uniqueCount);
49
50 // Step 4: Process array from right to left
51 const result: number[] = new Array(nums.length);
52 for (let i = nums.length - 1; i >= 0; i--) {
53 // Get compressed index of current number
54 const compressedIndex = valueToIndex.get(nums[i])!;
55
56 // Update BIT: mark this number as seen
57 update(compressedIndex, 1);
58
59 // Query how many numbers are smaller (indices 1 to compressedIndex-1)
60 result[i] = query(compressedIndex - 1);
61 }
62
63 return result;
64}
65
Time and Space Complexity
Time Complexity: O(n log n)
The time complexity breaks down as follows:
- Creating the sorted set of unique values:
O(n log n)
for sorting - Creating the mapping dictionary:
O(n)
- Processing each element in the reversed array:
O(n)
iterations- Each
update
operation:O(log n)
- walks up the tree following lowbit jumps, at mostlog n
levels - Each
query
operation:O(log n)
- walks down the tree following lowbit jumps, at mostlog n
levels
- Each
- Reversing the final answer:
O(n)
Total: O(n log n) + O(n) + O(n) * (O(log n) + O(log n)) + O(n) = O(n log n)
Space Complexity: O(n)
The space complexity consists of:
alls
sorted array:O(k)
wherek ≤ n
is the number of unique elements- Mapping dictionary
m
:O(k)
wherek ≤ n
- Binary Indexed Tree array
c
:O(k + 1) = O(k)
wherek ≤ n
- Answer array
ans
:O(n)
Total: O(k) + O(k) + O(k) + O(n) = O(n)
since k ≤ n
Common Pitfalls
1. Off-by-One Errors in Binary Indexed Tree Indexing
Pitfall: One of the most common mistakes is confusion between 0-indexed and 1-indexed systems. The Binary Indexed Tree traditionally uses 1-based indexing, but Python arrays are 0-indexed. Developers often make errors when:
- Forgetting to add 1 when mapping values to BIT indices
- Using 0 as a valid index in the BIT (which breaks the
lowbit
function) - Incorrectly querying
tree.query(x)
instead oftree.query(x - 1)
to count strictly smaller elements
Example of the mistake:
# Wrong: Using 0-based indexing for coordinate compression
value_to_rank = {value: rank for rank, value in enumerate(unique_sorted_values)} # Starts from 0!
# This causes issues because:
# - BIT expects indices starting from 1
# - lowbit(0) returns 0, causing infinite loops
Solution:
# Correct: Use 1-based indexing for coordinate compression
value_to_rank = {value: rank for rank, value in enumerate(unique_sorted_values, 1)}
# When querying for strictly smaller elements, use (rank - 1)
smaller_count = fenwick_tree.query(rank - 1)
2. Handling Duplicate Values Incorrectly
Pitfall: When the array contains duplicate values, the order of processing matters. If not handled carefully, duplicate values might be counted incorrectly when determining "smaller" elements.
Example scenario:
nums = [5, 2, 2, 1] # When processing the second 2, should the first 2 be counted as "smaller"? No! # The problem asks for strictly smaller elements
Solution: The current implementation handles this correctly by:
- Using coordinate compression that maps all occurrences of the same value to the same rank
- Querying
rank - 1
ensures we only count strictly smaller values, not equal ones - Processing from right to left maintains the correct positional relationship
3. Integer Overflow in Tree Array Size
Pitfall: Creating a BIT with size based on the maximum value in the array rather than the number of unique values can cause memory issues or crashes.
Example of the mistake:
# Wrong: Using max value directly
max_val = max(nums)
fenwick_tree = BinaryIndexedTree(max_val) # Could be huge if nums contains 10^9
Solution:
# Correct: Use coordinate compression to limit tree size
unique_sorted_values = sorted(set(nums))
value_to_rank = {value: rank for rank, value in enumerate(unique_sorted_values, 1)}
fenwick_tree = BinaryIndexedTree(len(value_to_rank)) # Size is at most n
4. Forgetting to Reverse the Result
Pitfall: Since we process the array from right to left (to count elements that appear after each position), the results are collected in reverse order. Forgetting the final reversal is a common mistake.
Example of the mistake:
def countSmaller(self, nums: List[int]) -> List[int]:
# ... processing logic ...
for value in reversed(nums):
# ... BIT operations ...
result.append(smaller_count)
return result # Wrong! This is in reverse order
Solution:
def countSmaller(self, nums: List[int]) -> List[int]:
# ... processing logic ...
for value in reversed(nums):
# ... BIT operations ...
result.append(smaller_count)
return result[::-1] # Correct: Reverse to match original array order
5. Incorrect Update Order
Pitfall: The order of operations within the loop is crucial. Updating the tree before querying would include the current element in its own count.
Example of the mistake:
# Wrong order: Query first, then update
for value in reversed(nums):
rank = value_to_rank[value]
smaller_count = fenwick_tree.query(rank - 1) # Query before update
fenwick_tree.update(rank, 1) # Update after query
result.append(smaller_count)
Correct approach:
# The implementation actually does update first, but queries (rank - 1)
# This is correct because we want strictly smaller elements
for value in reversed(nums):
rank = value_to_rank[value]
fenwick_tree.update(rank, 1) # Mark current element as seen
smaller_count = fenwick_tree.query(rank - 1) # Count smaller elements
result.append(smaller_count)
The key insight is that updating first and querying rank - 1
ensures we never count the current element itself, even if duplicates exist.
How would you design a stack which has a function min
that returns the minimum element in the stack, in addition to push
and pop
? All push
, pop
, min
should have running time O(1)
.
Recommended Readings
Segment Tree Faster Range Queries For this article we want to introduce the idea of a Segment Tree Segment Trees allow us to quickly perform range queries as well as range updates Suppose we have an array and we want to know the sum of a particular range of numbers as well
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
https assets algo monster cover_photos dnd svg Divide and Conquer The main idea of divide and conquer is to split the main problem into two smaller components assuming that each one of the components had already been solved recursively and then try to solve the bigger problem using the solutions
Want a Structured Path to Master System Design Too? Don’t Miss This!