2824. Count Pairs Whose Sum is Less than Target
Problem Description
You are given an integer array nums
with 0-based indexing (length n
) and an integer target
. Your task is to count how many pairs of indices (i, j)
satisfy the following conditions:
i
is less thanj
(i.e.,0 <= i < j < n
)- The sum of the elements at these indices is less than the target (i.e.,
nums[i] + nums[j] < target
)
The solution uses a sorting and binary search approach. After sorting the array, for each element at position j
, it uses binary search to find how many elements before position j
can form valid pairs with nums[j]
. Specifically, it finds the leftmost position where nums[i] >= target - nums[j]
, which means all positions before this can form valid pairs with the current element.
For example, if nums = [1, 2, 3, 4]
and target = 5
:
- When
j = 1
(element is 2), we search for elements less than5 - 2 = 3
before position 1, which gives us element at index 0 (value 1) - When
j = 2
(element is 3), we search for elements less than5 - 3 = 2
before position 2, which gives us element at index 0 (value 1) - When
j = 3
(element is 4), we search for elements less than5 - 4 = 1
before position 3, which gives us no valid elements
The total count accumulates all valid pairs found for each position j
.
Intuition
The key insight is that once we sort the array, we can efficiently find valid pairs using the sorted order property.
Think about it this way: if we fix the second element of the pair at position j
, we need to find all elements at positions i < j
such that nums[i] + nums[j] < target
. This is equivalent to finding all nums[i] < target - nums[j]
.
In a sorted array, all elements less than a certain value form a contiguous segment at the beginning. So instead of checking every element before position j
one by one (which would be inefficient), we can use binary search to quickly find the boundary - the first position where nums[i] >= target - nums[j]
. Everything before this boundary forms a valid pair with nums[j]
.
Why does this work? Because in a sorted array:
- If
nums[i] + nums[j] < target
for somei
, then all indices smaller thani
will also satisfy this condition (since those elements are even smaller) - We can find this "cutoff point" in logarithmic time using binary search
By iterating through each possible second element j
and using binary search to count valid first elements, we transform what could be an O(n²)
brute force approach into an O(n log n)
solution. The sorting step takes O(n log n)
and then we perform n
binary searches, each taking O(log n)
time.
Learn more about Two Pointers, Binary Search and Sorting patterns.
Solution Approach
The solution implements the sorting and binary search strategy in a clean and efficient manner.
Step 1: Sort the array
nums.sort()
We start by sorting the input array in ascending order. This enables us to use binary search later.
Step 2: Initialize counter
ans = 0
This variable will accumulate the total count of valid pairs.
Step 3: Iterate through each potential second element
for j, x in enumerate(nums):
We iterate through the sorted array, treating each element at position j
as the second element of potential pairs. The variable x
holds the value nums[j]
.
Step 4: Binary search for valid first elements
i = bisect_left(nums, target - x, hi=j)
For each position j
, we use bisect_left
to find the leftmost position in the range [0, j)
where we could insert target - x
while maintaining sorted order. This gives us the index of the first element that is greater than or equal to target - x
.
The hi=j
parameter ensures we only search in the range [0, j)
, which guarantees i < j
as required by the problem.
Step 5: Count valid pairs
ans += i
All indices from 0
to i-1
contain elements that are less than target - x
, meaning nums[k] + nums[j] < target
for all k
in [0, i)
. So we add i
to our answer (since there are i
valid indices: 0, 1, 2, ..., i-1).
Step 6: Return the result
return ans
After processing all possible second elements, we return the total count.
The time complexity is O(n log n)
for sorting plus O(n log n)
for n
binary searches, giving overall O(n log n)
. The space complexity is O(1)
if we don't count the sorting space (or O(n)
depending on the sorting algorithm used).
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 a concrete example with nums = [3, 1, 5, 2]
and target = 6
.
Step 1: Sort the array
After sorting: nums = [1, 2, 3, 5]
Step 2: Process each element as the second element of a pair
When j = 0 (nums[j] = 1):
- We need to find elements before index 0 where
nums[i] + 1 < 6
- This means finding
nums[i] < 5
in range[0, 0)
- Since the range is empty, no valid pairs exist
- Count: 0
When j = 1 (nums[j] = 2):
- We need to find elements before index 1 where
nums[i] + 2 < 6
- This means finding
nums[i] < 4
in range[0, 1)
- Binary search for 4 in
[1]
returns index 1 (where 4 would be inserted) - All elements from index 0 to 0 (just element 1) are valid
- Count: 1 valid pair → (0,1) which represents (1,2)
When j = 2 (nums[j] = 3):
- We need to find elements before index 2 where
nums[i] + 3 < 6
- This means finding
nums[i] < 3
in range[0, 2)
- Binary search for 3 in
[1, 2]
returns index 2 (where 3 would be inserted) - All elements from index 0 to 1 (elements 1 and 2) are valid
- Count: 2 valid pairs → (0,2) and (1,2) which represent (1,3) and (2,3)
When j = 3 (nums[j] = 5):
- We need to find elements before index 3 where
nums[i] + 5 < 6
- This means finding
nums[i] < 1
in range[0, 3)
- Binary search for 1 in
[1, 2, 3]
returns index 0 (where 1 is located) - No elements before index 0 satisfy the condition
- Count: 0 valid pairs
Total count: 0 + 1 + 2 + 0 = 3
The three valid pairs in the original array are:
- (1, 2) with sum 1 + 2 = 3 < 6
- (1, 3) with sum 1 + 3 = 4 < 6
- (2, 3) with sum 2 + 3 = 5 < 6
Solution Implementation
1from typing import List
2from bisect import bisect_left
3
4class Solution:
5 def countPairs(self, nums: List[int], target: int) -> int:
6 # Sort the array to enable binary search
7 nums.sort()
8
9 # Initialize counter for valid pairs
10 count = 0
11
12 # Iterate through each element as the second element of a pair
13 for j, num_j in enumerate(nums):
14 # Find the rightmost position where nums[i] < target - nums[j]
15 # This gives us the count of valid first elements for current second element
16 # We search only up to index j to avoid counting pairs twice and ensure i < j
17 left_bound = bisect_left(nums, target - num_j, hi=j)
18
19 # Add the count of valid pairs with nums[j] as the second element
20 count += left_bound
21
22 return count
23
1class Solution {
2 /**
3 * Counts the number of pairs (i, j) where i < j and nums[i] + nums[j] < target
4 *
5 * @param nums List of integers
6 * @param target Target sum value
7 * @return Number of valid pairs
8 */
9 public int countPairs(List<Integer> nums, int target) {
10 // Sort the list to enable binary search
11 Collections.sort(nums);
12
13 int pairCount = 0;
14
15 // For each element at index j, find how many elements before it
16 // can form a valid pair
17 for (int j = 0; j < nums.size(); ++j) {
18 int currentValue = nums.get(j);
19
20 // We need nums[i] + currentValue < target
21 // So we need nums[i] < target - currentValue
22 int threshold = target - currentValue;
23
24 // Find the leftmost position where nums[i] >= threshold
25 // All elements before this position form valid pairs with currentValue
26 int boundaryIndex = search(nums, threshold, j);
27
28 // Add the count of valid pairs for this j
29 pairCount += boundaryIndex;
30 }
31
32 return pairCount;
33 }
34
35 /**
36 * Binary search to find the leftmost index where nums[index] >= targetValue
37 * within the range [0, rightBound)
38 *
39 * @param nums Sorted list of integers
40 * @param targetValue The value to search for
41 * @param rightBound Exclusive right boundary for the search
42 * @return Index of the leftmost element >= targetValue, or rightBound if all elements < targetValue
43 */
44 private int search(List<Integer> nums, int targetValue, int rightBound) {
45 int left = 0;
46 int right = rightBound;
47
48 // Binary search for the leftmost position where nums[mid] >= targetValue
49 while (left < right) {
50 int mid = (left + right) >> 1; // Equivalent to (left + right) / 2
51
52 if (nums.get(mid) >= targetValue) {
53 // Found a candidate, but there might be a smaller index
54 right = mid;
55 } else {
56 // Current element is too small, search in the right half
57 left = mid + 1;
58 }
59 }
60
61 return left;
62 }
63}
64
1class Solution {
2public:
3 int countPairs(vector<int>& nums, int target) {
4 // Sort the array to enable binary search
5 sort(nums.begin(), nums.end());
6
7 int count = 0;
8
9 // For each element at index j, find how many elements before it
10 // can form a pair with sum less than target
11 for (int j = 0; j < nums.size(); ++j) {
12 // Find the first position where element >= (target - nums[j])
13 // All elements before this position will form valid pairs with nums[j]
14 int left_boundary = lower_bound(nums.begin(), nums.begin() + j, target - nums[j]) - nums.begin();
15
16 // Add the number of valid pairs with nums[j] as the second element
17 count += left_boundary;
18 }
19
20 return count;
21 }
22};
23
1/**
2 * Counts the number of pairs (i, j) where i < j and nums[i] + nums[j] < target
3 * @param nums - Array of integers
4 * @param target - Target sum value
5 * @returns Number of valid pairs
6 */
7function countPairs(nums: number[], target: number): number {
8 // Sort the array in ascending order for binary search
9 nums.sort((a, b) => a - b);
10
11 let pairCount = 0;
12
13 /**
14 * Binary search to find the leftmost index where nums[index] >= searchValue
15 * @param searchValue - The value to search for
16 * @param rightBoundary - The exclusive right boundary for search
17 * @returns The leftmost index where nums[index] >= searchValue
18 */
19 const binarySearch = (searchValue: number, rightBoundary: number): number => {
20 let left = 0;
21 let right = rightBoundary;
22
23 // Binary search for the leftmost position
24 while (left < right) {
25 const mid = Math.floor((left + right) / 2);
26
27 if (nums[mid] >= searchValue) {
28 // Move right pointer to mid to search left half
29 right = mid;
30 } else {
31 // Move left pointer to mid + 1 to search right half
32 left = mid + 1;
33 }
34 }
35
36 return left;
37 };
38
39 // For each element at index j, find how many elements before it can form valid pairs
40 for (let j = 0; j < nums.length; j++) {
41 // Find the number of elements i where i < j and nums[i] + nums[j] < target
42 // This means nums[i] < target - nums[j]
43 const validPairIndex = binarySearch(target - nums[j], j);
44
45 // Add the count of valid pairs with nums[j] as the second element
46 pairCount += validPairIndex;
47 }
48
49 return pairCount;
50}
51
Time and Space Complexity
Time Complexity: O(n × log n)
The time complexity consists of two main parts:
- Sorting the array
nums.sort()
takesO(n × log n)
time - The for loop iterates through all
n
elements, and for each element, it performs a binary search usingbisect_left()
which takesO(log n)
time. This gives usO(n × log n)
for the loop - Overall:
O(n × log n) + O(n × log n) = O(n × log n)
Space Complexity: O(log n)
The space complexity is determined by:
- The sorting algorithm (typically Timsort in Python) uses
O(log n)
space for its recursive call stack - The
bisect_left()
function usesO(1)
space as it's implemented iteratively - Other variables (
ans
,i
,j
,x
) useO(1)
space - Overall:
O(log 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: Forgetting to limit the binary search range
The Problem:
A common mistake is using bisect_left(nums, target - nums[j])
without the hi=j
parameter. This searches the entire array instead of just the elements before position j
.
Why it's wrong:
- It violates the constraint
i < j
by potentially finding indices greater than or equal toj
- It can count the same element pairing with itself when
nums[j] + nums[j] < target
- It may double-count pairs (counting both
(i,j)
and(j,i)
as separate pairs)
Incorrect Code:
for j, x in enumerate(nums):
i = bisect_left(nums, target - x) # Wrong! Searches entire array
ans += i
Correct Solution:
for j, x in enumerate(nums):
i = bisect_left(nums, target - x, hi=j) # Correct! Only searches [0, j)
ans += i
Pitfall 2: Using bisect_right instead of bisect_left
The Problem:
Using bisect_right
would find the rightmost position where we could insert target - x
, which gives us one position too far.
Why it's wrong:
If there are elements exactly equal to target - nums[j]
, we don't want to include them since nums[i] + nums[j]
would equal target
, not be less than it.
Incorrect Code:
i = bisect_right(nums, target - x - 1, hi=j) # Trying to compensate with -1
Correct Solution:
i = bisect_left(nums, target - x, hi=j) # Finds first element >= target - x
Pitfall 3: Incorrect handling when all elements before j are valid
The Problem:
Not understanding what bisect_left
returns when all elements in the search range are valid pairs.
Example scenario:
If nums = [1, 2, 8]
, target = 10
, and we're at j = 2
(element 8):
- We search for elements
< 10 - 8 = 2
in range[0, 2)
bisect_left
returns 1 (the position where 2 would be inserted)- This correctly indicates that only index 0 (element 1) forms a valid pair
Key Understanding:
bisect_left
returns the count of elements strictly less than the search value, which is exactly what we need.
Which of these properties could exist for a graph but not a tree?
Recommended Readings
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
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
Want a Structured Path to Master System Design Too? Don’t Miss This!