Count the pairs whose sum is below a target
You're given an integer array nums and an integer target, and you have to
return how many pairs of indices i < j have nums[i] + nums[j] < target.
Examples
| Input | Output | Why |
|---|---|---|
nums = [-1, 1, 2, 3, 1], target = 2 | 3 | You get (−1, 1) twice because there are two 1's, and then (−1, 2), and everything else comes to 2 or more. |
nums = [1, 2, 3], target = 0 | 0 | |
nums = [3, 1, 2], target = 4 | 1 | 1 + 2 is the only pair that's under 4, since the other two come to 4 and 5. |
Constraints
- 0 ≤ n ≤ 10⁵
- −10⁴ ≤ nums[i], target ≤ 10⁴
Hints
Hint 1
You're counting pairs of positions so it doesn't really matter what order the array's in. What does that let you do to it before you start?
Hint 2
I'd sort it and put lo at the start and hi at the end. If nums[lo] + nums[hi] < target then what can you say about nums[lo] with each of the values in between?
Hint 3
None of them is bigger than nums[hi], so all hi − lo of those pairs work and you can add them in one go and move lo up. When the sum's too big it's nums[hi] that can't go with anything that's left, and hi comes down.
Where people lose marks
- Adding 1 on a hit when you've really found hi − lo pairs, which will probably get the tiny examples right and the bigger ones wrong.
- Using ≤ where the problem says <, when a sum that's equal to the target doesn't count and on a tie it's hi that you'd move.
- Worrying that sorting changes the answer. I wouldn't, because it only moves the values about and every pair of positions you had before is still there with the same sum.
Dry run on [-1,1,2,3,1],2
Read it one frame at a time, the way you would trace the code on paper in an interview. Each frame shows the state after the step it describes.
-
Step 1 / 6nums-10lo11122334hicount0
Once it's sorted the array reads -1, 1, 1, 2, 3, and you're counting the pairs that add up to less than 2.
-
Step 2 / 6nums-10lo11122334hicount0
-1 + 3 is at least 2, so 3 is too big even next to the smallest value that's left and you can move hi left.
-
Step 3 / 6nums-10lo111223hi34count3
-1 + 2 < 2, so -1 works with all 3 values from index 1 to 3. You add 3 and move lo right.
-
Step 4 / 6nums-1011lo1223hi34count3
1 + 2 is at least 2, so 2 is too big even next to the smallest value that's left and you can move hi left.
-
Step 5 / 6nums-1011lo12hi2334count3
1 + 1 is at least 2, so 1 is too big even next to the smallest value that's left and you can move hi left.
-
Step 6 / 6nums-1011122334count3
The pointers have met and you've counted 3 pairs in all.
How to think about it
Checking every pair is O(n²). The count doesn't care where the values sit, so you're free to sort, and once the array's sorted you can use the same discard argument as the pair sum, with the difference that a hit usually gives you a whole run of pairs at once.
With lo at the start and hi at the end, if nums[lo] + nums[hi] < target
then nums[lo] plus anything at lo + 1, …, hi is under the target too, since those values
are all at most nums[hi]. That's hi − lo pairs counted in one step, and
nums[lo] has nothing more to give, so lo moves up. If the sum's at or over the target then
nums[hi] is too big to pair with even the smallest value left and hi comes down.
I think it's worth noticing that it's really the same shape as counting triangles, just with a single pair and no third value fixed on the outside, and you'll probably find the two easier to remember together.
Complexity. Time O(n log n) · Space O(1) extra (after an in-place sort)
The solution, in six languages
Each one is compiled and run against the test table before it is published. Java is reviewed by hand.
def count_pairs(nums, target):
a = sorted(nums)
lo, hi = 0, len(a) - 1
count = 0
while lo < hi:
if a[lo] + a[hi] < target:
count += hi - lo # a[lo] works with everything up to hi
lo += 1
else:
hi -= 1 # a[hi] is too big for anything left
return count#include <vector>
#include <algorithm>
long long solve(std::vector<int> a, int target) {
std::sort(a.begin(), a.end());
int lo = 0, hi = (int)a.size() - 1;
long long count = 0;
while (lo < hi) {
if (a[lo] + a[hi] < target) { count += hi - lo; ++lo; } // a[lo] works with everything up to hi
else --hi; // a[hi] is too big for anything left
}
return count;
}import java.util.Arrays;
class Solution {
public long countPairs(int[] nums, int target) {
int[] a = nums.clone();
Arrays.sort(a);
int lo = 0, hi = a.length - 1;
long count = 0;
while (lo < hi) {
if (a[lo] + a[hi] < target) { count += hi - lo; lo++; } // a[lo] works with everything up to hi
else hi--; // a[hi] is too big for anything left
}
return count;
}
}function countPairs(nums, target) {
const a = nums.slice().sort((x, y) => x - y);
let lo = 0, hi = a.length - 1, count = 0;
while (lo < hi) {
if (a[lo] + a[hi] < target) { count += hi - lo; lo++; } // a[lo] works with everything up to hi
else hi--; // a[hi] is too big for anything left
}
return count;
}fn solve(nums: &[i64], target: i64) -> i64 {
let mut a = nums.to_vec();
a.sort();
if a.len() < 2 { return 0; }
let (mut lo, mut hi) = (0usize, a.len() - 1);
let mut count = 0i64;
while lo < hi {
if a[lo] + a[hi] < target { count += (hi - lo) as i64; lo += 1; } // a[lo] works with everything up to hi
else { hi -= 1; } // a[hi] is too big for anything left
}
count
}import Data.Array
import Data.List (sort)
solve :: [Int] -> Int -> Int
solve nums target = go 0 (n - 1) 0
where
n = length nums
a = listArray (0, max 0 (n - 1)) (sort nums)
go lo hi acc
| lo >= hi = acc
| a ! lo + a ! hi < target = go (lo + 1) hi (acc + hi - lo) -- a ! lo works with everything up to hi
| otherwise = go lo (hi - 1) acc -- a ! hi is too big for anything left
Variants to try
- Count the pairs with a sum in a range [lo, hi]: count those below hi + 1 and take away those below lo.
- If the array's too big to sort, a Fenwick tree over the values counts the earlier values below target − x for each x in O(n log V).
Next problems
Pair with a given sum in a sorted array
The cleanest example of the two-pointer idea: sorted input, one pass, no extra memory.
Open the problem → Easy10 minA palindrome, ignoring punctuation and case
Compare from both ends inward, stepping over anything that is not a letter or a digit. No cleaned copy of the string is needed.
Open the problem → Easy12 minA palindrome after deleting at most one letter
Walk in from both ends. At the first mismatch one of the two letters has to go, so you try both and the rest has to match exactly.
Open the problem →Write a solution and run it against the real test table.
Keep going
Pair with a given sum in a sorted array
The cleanest example of the two-pointer idea: sorted input, one pass, no extra memory.
Open the problem → Easy10 minA palindrome, ignoring punctuation and case
Compare from both ends inward, stepping over anything that is not a letter or a digit. No cleaned copy of the string is needed.
Open the problem → Easy12 minA palindrome after deleting at most one letter
Walk in from both ends. At the first mismatch one of the two letters has to go, so you try both and the rest has to match exactly.
Open the problem → Two pointersThe pattern behind it
Two indices walking a sorted or paired structure, each step ruling out one candidate for good.
Read the pattern →Preparing for a real process? Quant interview preparation, or book a free 20-minute call.