2616. Minimize the Maximum Difference of Pairs
Problem Description
You have an integer array nums
(0-indexed) and an integer p
. Your task is to find p
pairs of indices from nums
such that:
- The maximum difference among all the pairs is as small as possible (minimized)
- No index can be used more than once across all pairs
For any pair of elements at indices i
and j
, the difference is calculated as |nums[i] - nums[j]|
(absolute value).
Your goal is to return the minimum possible value of the maximum difference among all p
pairs. If no pairs are selected (empty set), the maximum is defined as zero.
Example to clarify:
- If you have
nums = [1, 3, 5, 7]
and need to formp = 2
pairs - You might choose pairs like (index 0, index 1) with difference
|1-3| = 2
and (index 2, index 3) with difference|5-7| = 2
- The maximum difference among these pairs is
2
- The goal is to find the selection of pairs that makes this maximum as small as possible
The challenge is to strategically select which indices to pair together to achieve the smallest possible maximum difference while ensuring you can form exactly p
valid pairs without reusing any index.
Intuition
The key insight is recognizing that this problem has a monotonic property: if we can form p
pairs where the maximum difference is at most x
, then we can definitely form p
pairs where the maximum difference is at most x+1
(or any value larger than x
). This is because any valid solution for x
is also valid for x+1
.
This monotonic property immediately suggests binary search on the answer. We can search for the smallest value of maximum difference that allows us to form at least p
pairs.
But how do we check if a given maximum difference diff
is achievable? Here's where the second key insight comes in:
After sorting the array, adjacent elements will have the smallest differences. If we want to minimize the maximum difference, we should prefer pairing elements that are close to each other in the sorted array.
The greedy strategy emerges naturally: traverse the sorted array from left to right. At each position i
, if nums[i+1] - nums[i] <= diff
, we should immediately form this pair. Why? Because:
- This pair has an acceptable difference (≤
diff
) - Using
nums[i]
now with its closest neighbornums[i+1]
is optimal - any other pairing fornums[i]
would likely result in a larger difference - Once we use indices
i
andi+1
, we skip toi+2
to avoid reusing indices
If the difference exceeds our threshold, we skip nums[i]
and move to the next element. This greedy approach maximizes the number of pairs we can form for any given maximum difference threshold.
The combination of binary search (to find the minimum maximum difference) and greedy validation (to check if a difference is achievable) gives us an efficient solution.
Learn more about Greedy, Binary Search, Dynamic Programming and Sorting patterns.
Solution Approach
The solution combines binary search with a greedy validation function:
Step 1: Sort the array
nums.sort()
Sorting ensures that elements with smaller differences are adjacent, which is crucial for our greedy approach.
Step 2: Define the search range
The minimum possible maximum difference is 0
(when pairs have identical elements), and the maximum possible is nums[-1] - nums[0]
(the difference between the largest and smallest elements). So our search range is [0, nums[-1] - nums[0]]
.
Step 3: Binary search with validation
We use bisect_left
to find the smallest value where our validation function returns True
:
bisect_left(range(nums[-1] - nums[0] + 1), True, key=check)
Step 4: Greedy validation function
The check(diff)
function determines if we can form at least p
pairs with maximum difference ≤ diff
:
def check(diff: int) -> bool:
cnt = i = 0
while i < len(nums) - 1:
if nums[i + 1] - nums[i] <= diff:
cnt += 1 # Found a valid pair
i += 2 # Skip both indices (they're used)
else:
i += 1 # Skip only current index
return cnt >= p
How the greedy algorithm works:
- Start from the beginning of the sorted array
- At position
i
, check ifnums[i+1] - nums[i] <= diff
- If yes: form the pair
(i, i+1)
, increment pair count, and jump toi+2
- If no: skip
i
and try fromi+1
- After traversing, check if we formed at least
p
pairs
Why this greedy approach is optimal: For any given maximum difference threshold, pairing adjacent elements in the sorted array when possible maximizes the total number of pairs we can form. This is because:
- Adjacent elements have the smallest differences
- If we skip pairing
nums[i]
withnums[i+1]
when their difference is acceptable, any future pairing ofnums[i]
will likely have a larger difference - This strategy leaves more flexibility for forming pairs with the remaining elements
The binary search finds the minimum value of diff
where check(diff)
returns True
, giving us the minimum possible maximum difference.
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 = [10, 1, 2, 7, 1, 3]
and p = 2
.
Step 1: Sort the array
After sorting: nums = [1, 1, 2, 3, 7, 10]
Step 2: Set up binary search range
- Minimum possible difference:
0
- Maximum possible difference:
10 - 1 = 9
- Search range:
[0, 9]
Step 3: Binary search process
Let's trace through the binary search:
First iteration: mid = 4
- Check if we can form 2 pairs with max difference ≤ 4
- Greedy validation:
- i=0:
nums[1] - nums[0] = 1 - 1 = 0 ≤ 4
✓ Form pair (0,1), cnt=1, move to i=2 - i=2:
nums[3] - nums[2] = 3 - 2 = 1 ≤ 4
✓ Form pair (2,3), cnt=2, move to i=4 - i=4:
nums[5] - nums[4] = 10 - 7 = 3 ≤ 4
✓ Form pair (4,5), cnt=3
- i=0:
- Result: cnt=3 ≥ 2, so diff=4 works. Search lower half:
[0, 3]
Second iteration: mid = 1
- Check if we can form 2 pairs with max difference ≤ 1
- Greedy validation:
- i=0:
nums[1] - nums[0] = 1 - 1 = 0 ≤ 1
✓ Form pair (0,1), cnt=1, move to i=2 - i=2:
nums[3] - nums[2] = 3 - 2 = 1 ≤ 1
✓ Form pair (2,3), cnt=2, move to i=4 - i=4:
nums[5] - nums[4] = 10 - 7 = 3 > 1
✗ Skip i=4, move to i=5 - i=5: Out of bounds
- i=0:
- Result: cnt=2 ≥ 2, so diff=1 works. Search lower half:
[0, 0]
Third iteration: mid = 0
- Check if we can form 2 pairs with max difference ≤ 0
- Greedy validation:
- i=0:
nums[1] - nums[0] = 1 - 1 = 0 ≤ 0
✓ Form pair (0,1), cnt=1, move to i=2 - i=2:
nums[3] - nums[2] = 3 - 2 = 1 > 0
✗ Skip i=2, move to i=3 - i=3:
nums[4] - nums[3] = 7 - 3 = 4 > 0
✗ Skip i=3, move to i=4 - i=4:
nums[5] - nums[4] = 10 - 7 = 3 > 0
✗ Skip i=4, move to i=5 - i=5: Out of bounds
- i=0:
- Result: cnt=1 < 2, so diff=0 doesn't work
Binary search conclusion:
The minimum value where we can form at least 2 pairs is 1
.
Verification: With max difference = 1, we can form pairs:
- Pair 1: indices (0,1) with values (1,1), difference = 0
- Pair 2: indices (2,3) with values (2,3), difference = 1
The maximum difference among all pairs is 1, which is the minimum possible for this input.
Solution Implementation
1class Solution:
2 def minimizeMax(self, nums: List[int], p: int) -> int:
3 """
4 Find the minimum possible maximum difference among p pairs.
5
6 Args:
7 nums: List of integers
8 p: Number of pairs to form
9
10 Returns:
11 Minimum possible maximum difference among all pairs
12 """
13
14 def can_form_pairs_with_max_diff(max_diff: int) -> bool:
15 """
16 Check if we can form at least p pairs with maximum difference <= max_diff.
17 Uses greedy approach: if adjacent elements can form a valid pair, take it.
18
19 Args:
20 max_diff: Maximum allowed difference between pair elements
21
22 Returns:
23 True if at least p pairs can be formed, False otherwise
24 """
25 pair_count = 0
26 index = 0
27
28 # Greedily form pairs from adjacent elements in sorted array
29 while index < len(nums) - 1:
30 # If current and next element can form a valid pair
31 if nums[index + 1] - nums[index] <= max_diff:
32 pair_count += 1
33 index += 2 # Skip both elements as they're paired
34 else:
35 index += 1 # Move to next element
36
37 return pair_count >= p
38
39 # Sort array to ensure adjacent elements have minimum differences
40 nums.sort()
41
42 # Binary search on the answer (minimum maximum difference)
43 # Search space: [0, max_element - min_element]
44 left, right = 0, nums[-1] - nums[0]
45
46 while left < right:
47 mid = (left + right) // 2
48
49 # If we can form p pairs with max difference <= mid,
50 # try to find a smaller maximum difference
51 if can_form_pairs_with_max_diff(mid):
52 right = mid
53 else:
54 left = mid + 1
55
56 return left
57
1class Solution {
2 /**
3 * Finds the minimum possible maximum difference among p pairs from the array.
4 * Uses binary search on the answer space to find the minimum maximum difference.
5 *
6 * @param nums the input array of integers
7 * @param p the number of pairs to select
8 * @return the minimum possible maximum difference among all selected pairs
9 */
10 public int minimizeMax(int[] nums, int p) {
11 // Sort the array to enable greedy pairing strategy
12 Arrays.sort(nums);
13
14 int arrayLength = nums.length;
15
16 // Binary search boundaries: minimum difference is 0,
17 // maximum difference is the range of the array
18 int left = 0;
19 int right = nums[arrayLength - 1] - nums[0] + 1;
20
21 // Binary search to find the minimum valid maximum difference
22 while (left < right) {
23 // Calculate middle value using unsigned right shift to avoid overflow
24 int mid = (left + right) >>> 1;
25
26 // Check if we can form at least p pairs with max difference <= mid
27 if (count(nums, mid) >= p) {
28 // If yes, try to find a smaller maximum difference
29 right = mid;
30 } else {
31 // If no, we need a larger maximum difference
32 left = mid + 1;
33 }
34 }
35
36 return left;
37 }
38
39 /**
40 * Counts the maximum number of pairs that can be formed with difference <= maxDiff.
41 * Uses a greedy approach: if adjacent elements can form a valid pair, take them.
42 *
43 * @param nums the sorted array of integers
44 * @param diff the maximum allowed difference for a pair
45 * @return the maximum number of pairs with difference <= diff
46 */
47 private int count(int[] nums, int diff) {
48 int pairCount = 0;
49
50 // Iterate through the array and greedily form pairs
51 for (int i = 0; i < nums.length - 1; ++i) {
52 // Check if current element and next element form a valid pair
53 if (nums[i + 1] - nums[i] <= diff) {
54 // Form the pair and increment count
55 ++pairCount;
56 // Skip the next element as it's already paired
57 ++i;
58 }
59 }
60
61 return pairCount;
62 }
63}
64
1class Solution {
2public:
3 int minimizeMax(vector<int>& nums, int p) {
4 // Sort the array to make it easier to find pairs with minimum difference
5 sort(nums.begin(), nums.end());
6
7 int n = nums.size();
8
9 // Binary search on the answer
10 // left: minimum possible difference (0)
11 // right: maximum possible difference (largest - smallest) + 1
12 int left = 0;
13 int right = nums[n - 1] - nums[0] + 1;
14
15 // Lambda function to check if we can form p pairs with max difference <= targetDiff
16 auto canFormPairs = [&](int targetDiff) -> bool {
17 int pairCount = 0;
18
19 // Greedy approach: iterate through sorted array
20 for (int i = 0; i < n - 1; ++i) {
21 // If current and next element form a valid pair (difference <= targetDiff)
22 if (nums[i + 1] - nums[i] <= targetDiff) {
23 ++pairCount;
24 ++i; // Skip the next element as it's already paired
25 }
26 }
27
28 // Check if we can form at least p pairs
29 return pairCount >= p;
30 };
31
32 // Binary search to find the minimum maximum difference
33 while (left < right) {
34 int mid = (left + right) / 2;
35
36 if (canFormPairs(mid)) {
37 // If we can form p pairs with max diff = mid,
38 // try to find a smaller maximum difference
39 right = mid;
40 } else {
41 // If we cannot form p pairs with max diff = mid,
42 // we need a larger maximum difference
43 left = mid + 1;
44 }
45 }
46
47 return left;
48 }
49};
50
1/**
2 * Finds the minimum possible maximum difference among p pairs from the array
3 * @param nums - The input array of numbers
4 * @param p - The number of pairs to form
5 * @returns The minimized maximum difference among all pairs
6 */
7function minimizeMax(nums: number[], p: number): number {
8 // Sort the array in ascending order to consider adjacent elements for pairing
9 nums.sort((a: number, b: number) => a - b);
10
11 const arrayLength: number = nums.length;
12
13 // Binary search bounds: minimum difference is 0, maximum is the range of the array
14 let left: number = 0;
15 let right: number = nums[arrayLength - 1] - nums[0] + 1;
16
17 /**
18 * Checks if we can form at least p pairs with maximum difference <= maxDiff
19 * @param maxDiff - The maximum allowed difference between paired elements
20 * @returns True if p pairs can be formed with the given constraint
21 */
22 const canFormPairs = (maxDiff: number): boolean => {
23 let pairCount: number = 0;
24
25 // Greedily pair adjacent elements if their difference is within maxDiff
26 for (let i: number = 0; i < arrayLength - 1; i++) {
27 if (nums[i + 1] - nums[i] <= maxDiff) {
28 pairCount++;
29 i++; // Skip the next element as it's already paired
30 }
31 }
32
33 return pairCount >= p;
34 };
35
36 // Binary search to find the minimum maximum difference
37 while (left < right) {
38 const mid: number = Math.floor((left + right) / 2);
39
40 if (canFormPairs(mid)) {
41 // If we can form p pairs with mid as max difference, try smaller values
42 right = mid;
43 } else {
44 // If we cannot form p pairs, we need a larger max difference
45 left = mid + 1;
46 }
47 }
48
49 return left;
50}
51
Time and Space Complexity
Time Complexity: O(n × (log n + log m))
The time complexity consists of two main parts:
- Sorting the array takes
O(n log n)
time, wheren
is the length ofnums
- Binary search using
bisect_left
performsO(log m)
iterations, wherem = nums[-1] - nums[0]
(the difference between maximum and minimum values) - For each binary search iteration, the
check
function is called, which takesO(n)
time to iterate through the array - Therefore, the binary search portion contributes
O(n × log m)
time - Total time complexity:
O(n log n) + O(n × log m) = O(n × (log n + log m))
Space Complexity: O(1)
The space complexity is constant because:
- The sorting is done in-place (Python's sort modifies the original list)
- The
check
function only uses a constant amount of extra space for variablescnt
andi
- The binary search doesn't create any additional data structures proportional to the input size
- Only a fixed number of variables are used regardless of input size
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Not Handling Edge Case When p = 0
When p = 0
, no pairs need to be formed, so the maximum difference should be 0. The current implementation handles this correctly since the validation function will immediately return True
for any threshold when p = 0
, and binary search will return the minimum value (0).
2. Incorrect Greedy Strategy: Trying to Form "Optimal" Pairs Instead of Maximum Pairs
A common mistake is trying to be too clever with the validation function. For example:
# WRONG: Trying to find the "best" pairs within threshold
def check(diff):
used = set()
pairs = []
for i in range(len(nums)):
for j in range(i+1, len(nums)):
if nums[j] - nums[i] <= diff and i not in used and j not in used:
pairs.append((i, j))
used.add(i)
used.add(j)
return len(pairs) >= p
This approach is both inefficient (O(n²)) and incorrect. The greedy approach of always taking adjacent valid pairs in the sorted array is both optimal and efficient.
3. Off-by-One Error in Binary Search Range
A subtle pitfall is setting the wrong upper bound for binary search:
# WRONG: May miss the actual answer right = nums[-1] - nums[0] - 1 # Off by one # CORRECT: Include the full range right = nums[-1] - nums[0]
The maximum possible difference could be exactly nums[-1] - nums[0]
if we need to pair the smallest and largest elements.
4. Not Sorting the Array First
The greedy approach only works on a sorted array. Without sorting:
# WRONG: Forgetting to sort
def minimizeMax(self, nums, p):
# nums.sort() # Missing this crucial step!
def check(diff):
# ... greedy logic won't work correctly
The algorithm relies on adjacent elements having small differences, which is only guaranteed after sorting.
5. Using Wrong Comparison in Validation Function
Be careful with the comparison operator:
# WRONG: Using < instead of <= if nums[i + 1] - nums[i] < diff: # Should be <= # CORRECT: if nums[i + 1] - nums[i] <= diff:
Using <
would incorrectly reject valid pairs where the difference equals the threshold.
6. Misunderstanding the Problem: Trying to Form Exactly p Pairs
The validation function should check if we can form at least p
pairs, not exactly p
pairs:
# WRONG: Checking for exact count return cnt == p # CORRECT: Checking for at least p pairs return cnt >= p
We want to know if a given threshold is feasible, and having more valid pairs than needed still means the threshold works.
Is the following code DFS or BFS?
void search(Node root) { if (!root) return; visit(root); root.visited = true; for (Node node in root.adjacent) { if (!node.visited) { search(node); } } }
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
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
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
Want a Structured Path to Master System Design Too? Don’t Miss This!