AlgoMaster Logo

Count of Smaller Numbers After Self

hardFrequency7 min readUpdated June 23, 2026

Understanding the Problem

For each element in the array, we need to count how many elements to its right are strictly smaller. The result is an array of the same length where each position holds that count.

The direct method scans everything to the right of each element and counts. With up to 100,000 elements, that is an O(n^2) algorithm, up to 10 billion comparisons.

Processing the array from right to left reframes the problem as a sequence of rank queries: how many of the elements seen so far are smaller than the current one? A structure that answers rank queries in O(log n), such as a Binary Indexed Tree, solves it directly. Merge sort solves it from a different angle, by counting inversions while it sorts.

Key Constraints:

  • 1 <= nums.length <= 10^5 --> An O(n^2) scan is about 10 billion operations at the upper bound, so the target is O(n log n) or better.
  • -10^4 <= nums[i] <= 10^4 --> Only 20,001 distinct values are possible, so a structure indexed by value (such as a BIT over the whole value range) fits comfortably in memory.

Approach 1: Brute Force

Intuition

Scan every element to the right of each position and count how many are smaller. This translates the problem statement into two nested loops: for position i, iterate from i + 1 to the end, incrementing a counter each time a value is strictly less than nums[i].

Algorithm

  1. Create a result array of the same length as nums, initialized to 0.
  2. For each index i, iterate through indices j from i + 1 to n - 1 (for the last index this inner loop is empty and the count stays 0).
  3. If nums[j] < nums[i], increment result[i].
  4. Return the result array.

Example Walkthrough

1Initialize: result = [0, 0, 0, 0]
0
i
5
1
2
2
6
3
1
1/6

Code

Each scan starts from scratch; nothing learned while counting for one element carries over to the next. The remaining approaches share work across elements in two different ways: merge sort counts every qualifying pair as a side effect of sorting, and a Binary Indexed Tree answers each count incrementally in O(log n).

Approach 2: Merge Sort

Intuition

Counting smaller elements to the right is inversion counting. An inversion is a pair (i, j) where i < j but nums[i] > nums[j], and counts[i] is the number of inversions that have i as the left index.

Merge sort counts inversions as a byproduct of merging. When the merge step places an element from the left half into the merged result, every element already placed from the right half is smaller than it and sat to its right in the original array. Adding that running count to the left element's answer accumulates the result.

Sorting rearranges the elements, so each one carries its original index: we sort (value, original index) pairs and accumulate counts into the result array at the original indices.

Algorithm

  1. Create an array of pairs: (value, original_index) for each element.
  2. Create a result array initialized to 0.
  3. Perform merge sort on the pairs array.
  4. During the merge step, when placing an element from the left half into the merged result, add the number of already-placed elements from the right half to result[original_index].
  5. Return the result array.

Example Walkthrough

1Initial pairs: [(5,0), (2,1), (6,2), (1,3)], result=[0,0,0,0]
0
(5,0)
1
(2,1)
left half
2
(6,2)
3
(1,3)
right half
1/8

Code

Merge sort computes all the counts in one pass over the recursion, at the cost of threading original indices through the sort. The last approach skips sorting entirely: insert elements one at a time and query "how many values smaller than X are already present?" in O(log n).

Approach 3: Binary Indexed Tree (Fenwick Tree)

Intuition

Traverse the array from right to left. Before inserting each element, count how many already-inserted values are smaller. Every value inserted so far came from an index further right, so that count is the answer for the current position.

A Binary Indexed Tree (BIT) supports both required operations in O(log M), where M is the size of the index range: point update (add 1 at a position) and prefix sum query (sum of all values from index 1 to k). Index the BIT by value, treating it as a frequency table: inserting an element adds 1 at position value, and counting strictly smaller elements is the prefix sum from 1 to value - 1. Querying up to value - 1 rather than value is what excludes duplicates of the current element from the count.

Values can be negative (down to -10,000), so we add 10,001 to every value, mapping the range to [1, 20,001]. The shift starts at 1 rather than 0 because a BIT is 1-indexed: its traversal loops strip the lowest set bit until the index reaches 0, so index 0 can never store data.

The same approach works when values are unbounded. Sort the distinct values, replace each value with its rank (coordinate compression), and the BIT only needs n positions instead of one per possible value.

Algorithm

  1. Shift all values by adding an offset (10,001) so the minimum possible value maps to index 1.
  2. Create a BIT of size 20,003: the largest shifted value is 20,001, and an update starting there also touches index 20,002.
  3. Traverse nums from right to left.
  4. For each element, query the BIT for the prefix sum from 1 to shifted_value - 1 and store it in result[i]. This is the count of smaller elements already inserted.
  5. Update the BIT at position shifted_value by +1.
  6. Return the result array.

Example Walkthrough

1Initialize: BIT empty, result=[0,0,0,0]. Process right to left.
0
5
1
2
2
6
3
i
1
1/6

Code