1365. How Many Numbers Are Smaller Than the Current Number
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 indexj
is strictly less than the value at indexi
)
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
.
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:
- Create a sorted copy of the array
- 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 wherex
should be inserted inarr
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]
andx = 3
, thenbisect_left(arr, 3)
returns3
, 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 takesO(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 EvaluatorExample 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:
- Sorting the array using
sorted(nums)
takesO(n Γ log n)
time, wheren
is the length of the input array - The list comprehension performs
n
binary searches usingbisect_left()
, where each binary search takesO(log n)
time, resulting inO(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:
- The sorted array
arr
which stores a copy of alln
elements from the input, requiringO(n)
space - The output list created by the list comprehension, which also contains
n
elements, requiringO(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]
Which of the following is a min heap?
Recommended Readings
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donβt Miss This!