DSA SheetHard

SORTINGMERGE SORT

Count of Smaller Numbers After Self

HardEditorial · 8 minGenerated by gpt-5.6-luna · Aug 27

Open — the attempt gate is not wired up yet

This editorial is meant to unlock after you have run the problem at least once, with the worked solution behind one further deliberate click. That needs per-learner unlock state nothing stores today, so for now the whole article is open.

Try it yourself first →

Intuitionturning a right-side query into work done during merging

For nums[i], you need to count values to its right that are strictly smaller. Looking rightward separately for every index repeats the same comparisons and takes quadratic time. The useful change is to group indices into sorted ranges, where a whole block of values can be known to be smaller without checking its elements one by one.

Merge sort creates exactly the separation you need. During a merge, the left half contains earlier original indices and the right half contains later original indices. When a right-half value is moved before a left-half value, it is smaller than that left value and belongs to its right side. The number of right-half values already moved is therefore the count to add for that left element.

A merge of two sorted index ranges with smaller-value countsThe picture shows two sorted rows of paired values and original indices, with the left row above the right row. The right row contains later original positions. Several right-row values have already moved into the merged output before the current left-row element, while the current left element keeps its original index label. A count beside that element equals the number of moved right values, making visible why those values are smaller and lie to its right in the original array.1 (idx 0)4 (idx 3)8 (idx 6)239234 (idx 3)count += 2→ result[3] = 2+1 smaller+1 smallercurrent left: 4 (idx 3)write count 2 to slot 3left range — sorted by valueright range — later positionsmerged output so far — moved right values come firstEvery right value moved before the left value adds one; the count is exactly the numberalready moved.

Approachsix steps that preserve value order and original positions

  1. Store each value together with its original index, because sorting changes positions but counts must still be returned in the input order.
  2. Run merge sort on these pairs by value, because each merge puts a later-index half beside an earlier-index half where whole groups can be counted at once.
  3. Merge the sorted left and right halves with two pointers, because both halves are already sorted and the next smallest pair is always at one of those pointers.
  4. When the right value is strictly smaller, copy it first and advance the right pointer, because it has now passed every remaining left value and increases their future smaller-right count.
  5. When a left value is chosen, add j - mid - 1 to counts[arr[i].second], because exactly that many right-half values have already been copied before it; using <= keeps equal values from being counted as smaller.
  6. Copy every leftover left value with the same accumulated count, then copy the leftover right values and write the merged range back, because later merges require this range to remain sorted.

Complexitythe input arrangement does not change the merge bound

MEASUREBOUNDWHY
TimeO(n log n)There are log n levels of splitting, and each level copies every pair in the active ranges once during merging. The two pointers only move forward within their ranges, so no element is scanned repeatedly at one level; the worst value arrangement has the same bound.
SpaceO(n) extraThe pair array, temporary merge array, and counts array use linear storage; the returned counts array is required output and is excluded from the extra-space bound. The recursion adds O(log n) stack space, so the O(n) working arrays dominate, with no worse input shape because merge sort always splits by index.
Here n is the length of nums.

Annotated solutionC++ · stable merge sort · counts written by original index

CPPMerge sort counts right-half values already moved before each left-half value.
#include <vector>
#include <utility>

using namespace std;

class Solution {
public:
    vector<int> countSmaller(vector<int>& nums) {
        int n = static_cast<int>(nums.size());
        vector<int> counts(n, 0);
        vector<pair<int, int>> arr(n);

        for (int i = 0; i < n; ++i) {
            arr[i] = {nums[i], i};
        }

        vector<pair<int, int>> temp(n);
        mergeSort(arr, 0, n - 1, temp, counts);
        return counts;
    }

private:
    void mergeSort(vector<pair<int, int>>& arr, int left, int right,
                   vector<pair<int, int>>& temp, vector<int>& counts) {
        if (left >= right) {
            return;
        }

        int mid = left + (right - left) / 2;
        mergeSort(arr, left, mid, temp, counts);
        mergeSort(arr, mid + 1, right, temp, counts);
        merge(arr, left, mid, right, temp, counts);
    }

    void merge(vector<pair<int, int>>& arr, int left, int mid, int right,
               vector<pair<int, int>>& temp, vector<int>& counts) {
        int i = left;
        int j = mid + 1;
        int k = left;

        while (i <= mid && j <= right) {
            if (arr[i].first <= arr[j].first) {
                temp[k] = arr[i];
                counts[arr[i].second] += j - mid - 1;
                ++i;
            } else {
                temp[k] = arr[j];
                ++j;
            }
            ++k;
        }

        while (i <= mid) {
            temp[k] = arr[i];
            counts[arr[i].second] += j - mid - 1;
            ++i;
            ++k;
        }

        while (j <= right) {
            temp[k] = arr[j];
            ++j;
            ++k;
        }

        for (int p = left; p <= right; ++p) {
            arr[p] = temp[p];
        }
    }
};

The pair's second field is the key that prevents sorting from losing the answer's position. The merge count is added before that pair moves on, and the same count is added to leftover left pairs because every remaining right pair has already been moved before them. The <= comparison is deliberate: an equal right value is not smaller, so it must stay in the right half's count of moved values.

The Fenwick tree alternativea direct right-to-left query with the same asymptotic cost

A Fenwick tree gives a different arrangement of the same idea. Coordinate-compress the values into ranks, walk nums from right to left, and let the tree store how many processed values have each rank. The prefix sum before the current rank is exactly the number of strictly smaller values already seen, which are precisely the values to the right.

CPPFenwick tree alternative: query ranks smaller than the current value while scanning from right to left.
#include <algorithm>
#include <vector>

using namespace std;

class Solution {
public:
    vector<int> countSmaller(vector<int>& nums) {
        vector<int> values = nums;
        sort(values.begin(), values.end());
        values.erase(unique(values.begin(), values.end()), values.end());

        int m = static_cast<int>(values.size());
        vector<int> tree(m + 1, 0);
        vector<int> counts(nums.size(), 0);

        for (int i = static_cast<int>(nums.size()) - 1; i >= 0; --i) {
            int rank = static_cast<int>(
                lower_bound(values.begin(), values.end(), nums[i]) - values.begin()
            ) + 1;

            counts[i] = query(tree, rank - 1);
            add(tree, rank, 1);
        }

        return counts;
    }

private:
    void add(vector<int>& tree, int index, int amount) {
        while (index < static_cast<int>(tree.size())) {
            tree[index] += amount;
            index += index & -index;
        }
    }

    int query(const vector<int>& tree, int index) {
        int total = 0;
        while (index > 0) {
            total += tree[index];
            index -= index & -index;
        }
        return total;
    }
};

This is not an asymptotic optimisation: coordinate compression takes O(n log n), and each query and update takes O(log n), so the total remains O(n log n) with O(n) extra space. It buys a direct expression of the question, while merge sort avoids a separate rank structure and naturally explains the inversion count. Choose the tree when point updates and prefix queries already fit your toolkit.

Common mistakesthree lines that quietly change the meaning of smaller

Previous · Count Inversions