Facebook Pixel

1365. How Many Numbers Are Smaller Than the Current Number

EasyArrayHash TableCounting SortSorting
Leetcode Link

Problem Description

You are given an array nums containing integers. For each element in the array, you need to count how many other elements in the array are smaller than it.

Specifically, for each nums[i], count the number of valid indices j where:

  • j != i (the index is different from the current element's index)
  • nums[j] < nums[i] (the value at index j is strictly less than the value at index i)

Return an array where each position contains the count of smaller elements for the corresponding position in the original array.

For example, if nums = [8, 1, 2, 2, 3], the output would be [4, 0, 1, 1, 3] because:

  • For nums[0] = 8: there are 4 smaller elements (1, 2, 2, 3)
  • For nums[1] = 1: there are 0 smaller elements
  • For nums[2] = 2: there is 1 smaller element (1)
  • For nums[3] = 2: there is 1 smaller element (1)
  • For nums[4] = 3: there are 3 smaller elements (1, 2, 2)

The solution uses sorting and binary search. It creates a sorted copy of the array arr, then for each element x in the original array, uses bisect_left to find the position where x would be inserted in the sorted array. This position represents exactly how many elements are smaller than x.

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

Intuition

The key insight is that if we sort the array, the position of an element in the sorted array tells us exactly how many elements are smaller than it.

Think about it this way: when we sort an array in ascending order, the smallest element goes to position 0, the second smallest to position 1, and so on. So if an element ends up at position k in the sorted array, it means there are exactly k elements smaller than it.

For example, with nums = [8, 1, 2, 2, 3], the sorted array would be [1, 2, 2, 3, 8]. The element 8 is at index 4 in the sorted array, which means there are 4 elements smaller than it.

However, we can't just sort the original array and return the indices because we need to maintain the original order of elements in our answer. So we:

  1. Create a sorted copy of the array
  2. For each element in the original array, find where it would be placed in the sorted array

The position where an element would be inserted in a sorted array is exactly the count of elements smaller than it. This is what bisect_left does - it finds the leftmost position where we could insert an element to maintain sorted order. This position equals the number of elements that are strictly less than our target element.

The reason we use bisect_left instead of bisect_right is to handle duplicates correctly. When there are duplicate values, bisect_left gives us the position of the first occurrence, which correctly counts only the elements that are strictly smaller than our target value.

Learn more about Sorting patterns.

Solution Approach

The solution uses sorting combined with binary search to efficiently count smaller elements for each position.

Step 1: Create a sorted copy

arr = sorted(nums)

We create a new array arr that contains all elements from nums but in sorted ascending order. This sorted array serves as our reference to count smaller elements.

Step 2: Use binary search for each element

return [bisect_left(arr, x) for x in nums]

For each element x in the original nums array, we use bisect_left(arr, x) to find its position in the sorted array.

How bisect_left works:

  • bisect_left(arr, x) returns the leftmost position where x should be inserted in arr to maintain sorted order
  • This position is exactly equal to the count of elements that are strictly less than x
  • For example, if arr = [1, 2, 2, 3, 8] and x = 3, then bisect_left(arr, 3) returns 3, meaning there are 3 elements smaller than 3

Time Complexity Analysis:

  • Sorting the array takes O(n log n) time
  • For each of the n elements, we perform a binary search which takes O(log n) time
  • Total time complexity: O(n log n) + O(n * log n) = O(n log n)

Space Complexity:

  • We create a sorted copy of the array, which takes O(n) space
  • The result array also takes O(n) space
  • Total space complexity: O(n)

Why this approach works for duplicates: When there are duplicate values in the array, bisect_left always returns the index of the first occurrence in the sorted array. This ensures we only count elements that are strictly smaller than the current element, not equal to it.

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

Step 1: Create a sorted copy

  • Original array: nums = [5, 2, 6, 1]
  • Sorted array: arr = [1, 2, 5, 6]

Step 2: Process each element using binary search

For nums[0] = 5:

  • Use bisect_left(arr, 5) on [1, 2, 5, 6]
  • Binary search finds that 5 should be inserted at index 2
  • This means there are 2 elements smaller than 5 (which are 1 and 2)
  • Result: 2

For nums[1] = 2:

  • Use bisect_left(arr, 2) on [1, 2, 5, 6]
  • Binary search finds that 2 should be inserted at index 1
  • This means there is 1 element smaller than 2 (which is 1)
  • Result: 1

For nums[2] = 6:

  • Use bisect_left(arr, 6) on [1, 2, 5, 6]
  • Binary search finds that 6 should be inserted at index 3
  • This means there are 3 elements smaller than 6 (which are 1, 2, and 5)
  • Result: 3

For nums[3] = 1:

  • Use bisect_left(arr, 1) on [1, 2, 5, 6]
  • Binary search finds that 1 should be inserted at index 0
  • This means there are 0 elements smaller than 1
  • Result: 0

Final answer: [2, 1, 3, 0]

Let's verify:

  • 5 has 2 smaller elements (2, 1) βœ“
  • 2 has 1 smaller element (1) βœ“
  • 6 has 3 smaller elements (5, 2, 1) βœ“
  • 1 has 0 smaller elements βœ“

Solution Implementation

1from typing import List
2from bisect import bisect_left
3
4class Solution:
5    def smallerNumbersThanCurrent(self, nums: List[int]) -> List[int]:
6        # Create a sorted copy of the input array
7        # This allows us to use binary search to find positions
8        sorted_nums = sorted(nums)
9      
10        # For each number in the original array, find how many numbers are smaller
11        # bisect_left returns the leftmost position where the number would be inserted
12        # This position equals the count of numbers smaller than the current number
13        result = [bisect_left(sorted_nums, num) for num in nums]
14      
15        return result
16
1class Solution {
2    /**
3     * Given an array nums, for each nums[i], find the number of elements smaller than it.
4     * @param nums the input array
5     * @return array where each element represents count of smaller numbers than the corresponding element in nums
6     */
7    public int[] smallerNumbersThanCurrent(int[] nums) {
8        // Create a copy of the original array for sorting
9        int[] sortedArray = nums.clone();
10      
11        // Sort the copied array in ascending order
12        Arrays.sort(sortedArray);
13      
14        // For each element in the original array, find its position in the sorted array
15        // The position represents the count of smaller elements
16        for (int i = 0; i < nums.length; ++i) {
17            nums[i] = search(sortedArray, nums[i]);
18        }
19      
20        return nums;
21    }
22
23    /**
24     * Binary search to find the leftmost position of target value in a sorted array.
25     * This position equals the count of elements smaller than the target.
26     * @param nums the sorted array to search in
27     * @param target the value to search for
28     * @return the index of the first occurrence of target (count of smaller elements)
29     */
30    private int search(int[] nums, int target) {
31        int left = 0;
32        int right = nums.length;
33      
34        // Binary search for the leftmost position of target
35        while (left < right) {
36            int mid = (left + right) >> 1;  // Equivalent to (left + right) / 2
37          
38            if (nums[mid] >= target) {
39                // Target is in the left half or at mid, narrow down the right boundary
40                right = mid;
41            } else {
42                // Target is in the right half, narrow down the left boundary
43                left = mid + 1;
44            }
45        }
46      
47        return left;
48    }
49}
50
1class Solution {
2public:
3    vector<int> smallerNumbersThanCurrent(vector<int>& nums) {
4        // Create a copy of the original array for sorting
5        vector<int> sortedArray = nums;
6      
7        // Sort the copied array in ascending order
8        sort(sortedArray.begin(), sortedArray.end());
9      
10        // For each element in the original array, find how many elements are smaller
11        for (int i = 0; i < nums.size(); ++i) {
12            // Use binary search to find the first occurrence of nums[i] in sorted array
13            // The index of first occurrence equals the count of smaller elements
14            nums[i] = lower_bound(sortedArray.begin(), sortedArray.end(), nums[i]) - sortedArray.begin();
15        }
16      
17        // Return the modified array containing counts of smaller elements
18        return nums;
19    }
20};
21
1/**
2 * Counts how many numbers are smaller than each element in the array
3 * @param nums - Input array of numbers
4 * @returns Array where each element represents count of smaller numbers than corresponding element in input
5 */
6function smallerNumbersThanCurrent(nums: number[]): number[] {
7    /**
8     * Binary search to find the leftmost position where target can be inserted
9     * @param sortedArray - Sorted array to search in
10     * @param target - Target value to find position for
11     * @returns Index representing count of elements smaller than target
12     */
13    const binarySearch = (sortedArray: number[], target: number): number => {
14        let left: number = 0;
15        let right: number = sortedArray.length;
16      
17        // Binary search for leftmost position where target fits
18        while (left < right) {
19            const mid: number = Math.floor((left + right) / 2);
20          
21            if (sortedArray[mid] >= target) {
22                // Target is in left half or at mid, move right boundary
23                right = mid;
24            } else {
25                // Target is in right half, move left boundary
26                left = mid + 1;
27            }
28        }
29      
30        return left;
31    };
32  
33    // Create a sorted copy of the input array
34    const sortedArray: number[] = [...nums].sort((a: number, b: number) => a - b);
35  
36    // For each element, find count of smaller elements using binary search
37    const result: number[] = [];
38    for (let i: number = 0; i < nums.length; i++) {
39        result[i] = binarySearch(sortedArray, nums[i]);
40    }
41  
42    return result;
43}
44

Time and Space Complexity

Time Complexity: O(n Γ— log n)

The time complexity is dominated by two operations:

  1. Sorting the array using sorted(nums) takes O(n Γ— log n) time, where n is the length of the input array
  2. The list comprehension performs n binary searches using bisect_left(), where each binary search takes O(log n) time, resulting in O(n Γ— log n) total time

Since both operations have the same complexity, the overall time complexity is O(n Γ— log n).

Space Complexity: O(n)

The space complexity comes from:

  1. The sorted array arr which stores a copy of all n elements from the input, requiring O(n) space
  2. The output list created by the list comprehension, which also contains n elements, requiring O(n) space

Therefore, the total space complexity is O(n), where n is the length of the array nums.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall 1: Confusing bisect_left with bisect_right for duplicate values

The Problem: A common mistake is using bisect_right instead of bisect_left, or not understanding the difference when dealing with duplicates. This would give incorrect counts for duplicate elements.

Example of the issue:

nums = [8, 1, 2, 2, 3]
sorted_nums = [1, 2, 2, 3, 8]

# Using bisect_right (WRONG):
bisect_right(sorted_nums, 2)  # Returns 3 (includes both 2's)
# This would incorrectly count the other 2 as "smaller"

# Using bisect_left (CORRECT):
bisect_left(sorted_nums, 2)   # Returns 1 (position before all 2's)
# This correctly counts only elements strictly less than 2

Solution: Always use bisect_left for this problem since we need to count elements strictly less than the current element. bisect_left returns the leftmost insertion point, which equals the count of smaller elements.

Pitfall 2: Modifying the original array instead of creating a sorted copy

The Problem: Sorting the original array in-place would destroy the original order, making it impossible to map results back to the correct positions.

Incorrect approach:

def smallerNumbersThanCurrent(self, nums: List[int]) -> List[int]:
    nums.sort()  # WRONG: This modifies the original array
    return [bisect_left(nums, x) for x in nums]
    # This would return [0, 1, 2, 3, 4] for any sorted input

Solution: Always create a separate sorted copy:

sorted_nums = sorted(nums)  # Creates a new sorted array
# Original nums array remains unchanged

Pitfall 3: Not handling edge cases properly

The Problem: Forgetting to test edge cases like empty arrays, single-element arrays, or arrays with all identical elements.

Edge cases to consider:

# Empty array
nums = []  # Should return []

# Single element
nums = [5]  # Should return [0]

# All elements identical
nums = [3, 3, 3, 3]  # Should return [0, 0, 0, 0]

# Negative numbers
nums = [-1, -2, 3, 0]  # Should work correctly with negatives

Solution: The binary search approach naturally handles these edge cases correctly, but it's important to verify:

def smallerNumbersThanCurrent(self, nums: List[int]) -> List[int]:
    if not nums:  # Handle empty array explicitly if needed
        return []
  
    sorted_nums = sorted(nums)
    return [bisect_left(sorted_nums, num) for num in nums]
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following is a min heap?


Recommended Readings

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

Load More