Facebook Pixel

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], and 2 and 1 are smaller than 5, so counts[0] = 2
  • For nums[1] = 2: elements to the right are [6, 1], and only 1 is smaller than 2, so counts[1] = 1
  • For nums[2] = 6: elements to the right are [1], and 1 is smaller than 6, so counts[2] = 1
  • For nums[3] = 1: no elements to the right, so counts[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:

  1. 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.

  2. 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
  3. Binary Indexed Tree Operations:

    • update(x, delta): Adds delta to position x in the tree
    • query(x): Returns the sum of all elements from position 1 to x
    • 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. Efficient insertion of elements as we see them
  2. 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:

  1. lowbit(x): Returns x & -x, which isolates the rightmost set bit. For example, lowbit(6) = 2 because 6 in binary is 110, and the rightmost set bit represents 2.

  2. update(x, delta): Adds delta to position x and propagates the update upward in the tree structure. Starting from index x, it moves to the next responsible node by adding lowbit(x) until it exceeds n.

  3. query(x): Computes the prefix sum from index 1 to x. It accumulates values by moving downward in the tree, subtracting lowbit(x) at each step until reaching 0.

Main Solution Logic:

  1. 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)]
  2. Initialize the Tree:

    tree = BinaryIndexedTree(len(m))

    Create a Binary Indexed Tree with size equal to the number of unique values.

  3. 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 index x
    • Update the tree at position x (increment frequency by 1)
    • Query the tree for sum of positions [1, x-1] to count smaller elements
    • Append this count to the answer list
  4. 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 position 1, query [1,0] = 0
  • Process 6: update position 4, query [1,3] = 1 (only 1 is smaller)
  • Process 2: update position 2, query [1,1] = 1 (only 1 is smaller)
  • Process 5: update position 3, query [1,2] = 2 (both 1 and 2 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 Evaluator

Example 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 most log n levels
    • Each query operation: O(log n) - walks down the tree following lowbit jumps, at most log n levels
  • 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) where k ≤ n is the number of unique elements
  • Mapping dictionary m: O(k) where k ≤ n
  • Binary Indexed Tree array c: O(k + 1) = O(k) where k ≤ 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 of tree.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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

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

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More