HardDivide & Conquer

Count of Smaller Numbers After Self โ€” Solution

Problem

Given an array of integers, return a new array where each position holds the count of elements that are strictly smaller and appear to the right of the original element.

  • Input: nums = [5, 2, 6, 1]
  • Output: [2, 1, 1, 0]
  • Explanation: To the right of 5 are two smaller values (2 and 1); to the right of 6 is one (1); 1 has nothing after it.

Counter-example: [3, 3, 3] โ†’ [0, 0, 0] โ€” equal elements do not count as smaller.

Intuition

For each element we need to count how many values to its right are strictly smaller. A brute-force scan works but re-examines the same pairs repeatedly. The key insight is that merge sort already compares every left-half element against every right-half element while merging โ€” we can piggyback the count on those comparisons at no extra asymptotic cost, turning O(nยฒ) into O(n log n).

Approach 1 โ€” Brute Force

For each element, scan every element to its right and count those that are strictly smaller.

  1. Initialize a counts array of zeros with the same length as nums.
  2. For each index i from 0 to n-2, iterate j from i+1 to n-1.
  3. If nums[j] < nums[i], increment counts[i].
  4. Return counts.
1def countSmaller(nums: list[int]) -> list[int]:
2    n = len(nums)
3    counts = [0] * n
4    for i in range(n - 1):
5        for j in range(i + 1, n):
6            if nums[j] < nums[i]:
7                counts[i] += 1
8    return counts

Time: O(nยฒ) โ€” every pair (i, j) with i < j is compared exactly once.
Space: O(n) โ€” only the output array; no extra work space.

Approach 2 โ€” Modified Merge Sort

Augment merge sort to count inversions: when a right-half element is placed before left-half elements during merging, it is smaller than all those not-yet-merged left elements โ€” so each earns a +1.

  1. Pair each value with its original index so positions survive reordering during sort.
  2. Recursively sort the left and right halves.
  3. During merge, maintain right_picks โ€” how many right-half elements have been placed so far.
  4. When a right element is smaller than the current left element, append it and increment right_picks.
  5. When a left element is taken, add right_picks to that element's count (those right elements are all smaller and originally appeared to its right).
  6. After the main loop, flush any remaining left elements โ€” each still earns the final right_picks.
1def countSmaller(nums: list[int]) -> list[int]:
2    n = len(nums)
3    counts = [0] * n
4    # pair each element with its original index to write results to the right slot after sorting
5    pairs = list(enumerate(nums))
6
7    def merge_sort(arr: list) -> list:
8        if len(arr) <= 1:
9            return arr
10        mid = len(arr) // 2
11        left = merge_sort(arr[:mid])
12        right = merge_sort(arr[mid:])
13        return merge(left, right)
14
15    def merge(left: list, right: list) -> list:
16        result = []
17        left_ptr = right_ptr = 0
18        right_picks = 0  # right elements placed before any remaining left elements
19
20        while left_ptr < len(left) and right_ptr < len(right):
21            orig_left_idx, left_val = left[left_ptr]
22            _, right_val = right[right_ptr]
23            if right_val < left_val:
24                result.append(right[right_ptr])
25                right_ptr += 1
26                right_picks += 1  # one more right element is smaller than all remaining left
27            else:
28                counts[orig_left_idx] += right_picks  # right_picks right elements are all smaller
29                result.append(left[left_ptr])
30                left_ptr += 1
31
32        while left_ptr < len(left):  # flush remaining left elements
33            orig_left_idx, _ = left[left_ptr]
34            counts[orig_left_idx] += right_picks
35            result.append(left[left_ptr])
36            left_ptr += 1
37
38        result.extend(right[right_ptr:])
39        return result
40
41    merge_sort(pairs)
42    return counts

Time: O(n log n) โ€” standard merge sort with O(1) extra work per element comparison.
Space: O(n) โ€” the temporary merge buffer and recursion stack are both proportional to n.

Complexity Summary

ApproachTimeSpaceWhen to use
Brute ForceO(nยฒ)O(n)Only for very small inputs or as a correctness baseline
Modified Merge SortO(n log n)O(n)General case โ€” handles duplicates and large arrays efficiently

Common Mistakes

  • Forgetting to track original indices โ€” once elements are rearranged by merge sort, their positions change; pairing each value with its original index before sorting is what lets you write into the correct counts slot instead of a wrong one.
  • Not flushing remaining left elements after the merge loop exits โ€” when the right subarray empties first, the while loop ends, but remaining left elements still need right_picks added; omitting that flush loop silently undercounts for those positions.
  • Using <= instead of < in the merge comparison โ€” the problem asks for strictly smaller; using <= would treat equal elements as smaller than each other and inflate counts wherever duplicates appear to the right.
  • Sorting nums in place instead of working on index-value pairs โ€” modifying the original array destroys the positional information needed to write results into the correct counts index.
  • Applying right_picks to right-half elements instead of left-half ones โ€” it's the left-half elements whose counts grow when right-half elements are placed before them, not the other way around; swapping the update target produces results for the wrong half.

Related Problems

  • reverse-pairs โ€” same divide-and-conquer inversion framework, but the comparison uses a multiplied threshold instead of a direct value
  • sort-list โ€” isolates the merge sort mechanics on a linked list without the counting layer, good for building the merge intuition
  • merge-k-sorted-lists โ€” generalizes the merge step to k sorted sequences using the same comparison logic
  • longest-increasing-subsequence โ€” counting ordered elements to the left/right shares the "rank elements by value" perspective
  • kth-largest-element-in-an-array โ€” finding the rank of a single element, a simpler form of the same order-statistic question

Ready to practice? Try it on SkillFlow

Adaptive problems, AI follow-up interviews, and a skill score that shows exactly where you need to improve.

Practice This Problem โ†’