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 = 2pairs - You might choose pairs like (index 0, index 1) with difference
|1-3| = 2and (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
iandi+1, we skip toi+2to 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 Implementation
1class Solution:
2 def minimizeMax(self, nums: List[int], p: int) -> int:
3 def feasible(max_diff: int) -> bool:
4 """Check if we can form at least p pairs with max difference <= max_diff."""
5 pair_count = 0
6 index = 0
7
8 while index < len(nums) - 1:
9 if nums[index + 1] - nums[index] <= max_diff:
10 pair_count += 1
11 index += 2
12 else:
13 index += 1
14
15 return pair_count >= p
16
17 nums.sort()
18
19 left, right = 0, nums[-1] - nums[0]
20 first_true_index = -1
21
22 while left <= right:
23 mid = (left + right) // 2
24 if feasible(mid):
25 first_true_index = mid
26 right = mid - 1
27 else:
28 left = mid + 1
29
30 return first_true_index if first_true_index != -1 else 0
311class Solution {
2 public int minimizeMax(int[] nums, int p) {
3 Arrays.sort(nums);
4
5 int left = 0;
6 int right = nums[nums.length - 1] - nums[0];
7 int firstTrueIndex = -1;
8
9 while (left <= right) {
10 int mid = left + (right - left) / 2;
11 if (feasible(nums, p, mid)) {
12 firstTrueIndex = mid;
13 right = mid - 1;
14 } else {
15 left = mid + 1;
16 }
17 }
18
19 return firstTrueIndex != -1 ? firstTrueIndex : 0;
20 }
21
22 private boolean feasible(int[] nums, int p, int maxDiff) {
23 int pairCount = 0;
24 for (int i = 0; i < nums.length - 1; ++i) {
25 if (nums[i + 1] - nums[i] <= maxDiff) {
26 ++pairCount;
27 ++i;
28 }
29 }
30 return pairCount >= p;
31 }
32}
331class Solution {
2public:
3 int minimizeMax(vector<int>& nums, int p) {
4 sort(nums.begin(), nums.end());
5
6 int n = nums.size();
7 int left = 0;
8 int right = nums[n - 1] - nums[0];
9 int firstTrueIndex = -1;
10
11 auto feasible = [&](int maxDiff) -> bool {
12 int pairCount = 0;
13 for (int i = 0; i < n - 1; ++i) {
14 if (nums[i + 1] - nums[i] <= maxDiff) {
15 ++pairCount;
16 ++i;
17 }
18 }
19 return pairCount >= p;
20 };
21
22 while (left <= right) {
23 int mid = left + (right - left) / 2;
24 if (feasible(mid)) {
25 firstTrueIndex = mid;
26 right = mid - 1;
27 } else {
28 left = mid + 1;
29 }
30 }
31
32 return firstTrueIndex != -1 ? firstTrueIndex : 0;
33 }
34};
351function minimizeMax(nums: number[], p: number): number {
2 nums.sort((a, b) => a - b);
3
4 const feasible = (maxDiff: number): boolean => {
5 let pairCount = 0;
6 for (let i = 0; i < nums.length - 1; i++) {
7 if (nums[i + 1] - nums[i] <= maxDiff) {
8 pairCount++;
9 i++;
10 }
11 }
12 return pairCount >= p;
13 };
14
15 let left = 0;
16 let right = nums[nums.length - 1] - nums[0];
17 let firstTrueIndex = -1;
18
19 while (left <= right) {
20 const mid = Math.floor((left + right) / 2);
21 if (feasible(mid)) {
22 firstTrueIndex = mid;
23 right = mid - 1;
24 } else {
25 left = mid + 1;
26 }
27 }
28
29 return firstTrueIndex !== -1 ? firstTrueIndex : 0;
30}
31Solution Approach
The solution combines binary search with a greedy validation function:
Step 1: Sort the array Sorting ensures that elements with smaller differences are adjacent, which is crucial for our greedy approach.
Step 2: Binary Search Template
We use the first_true_index template to find the minimum maximum difference:
- Initialize
left = 0andright = nums[-1] - nums[0] - Track the answer with
first_true_index = -1 - Use
while left <= rightloop structure - When
feasible(mid)returnsTrue, updatefirst_true_index = midand search left withright = mid - 1 - When
feasible(mid)returnsFalse, search right withleft = mid + 1
Step 3: Feasible Function (Greedy Validation)
The feasible(max_diff) function determines if we can form at least p pairs with maximum difference ≤ max_diff:
def feasible(max_diff: int) -> bool:
pair_count = index = 0
while index < len(nums) - 1:
if nums[index + 1] - nums[index] <= max_diff:
pair_count += 1
index += 2
else:
index += 1
return pair_count >= p
Why the Greedy Approach Works: For any given maximum difference threshold, pairing adjacent elements in the sorted array when possible maximizes the total number of pairs. Adjacent elements have the smallest differences, and skipping a valid pair only hurts future pairing options.
Edge Case Handling:
When p = 0, no pairs are needed, so the answer is 0. The template handles this gracefully since feasible(0) returns True when p = 0.
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.
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, wherenis the length ofnums - Binary search using
bisect_leftperformsO(log m)iterations, wherem = nums[-1] - nums[0](the difference between maximum and minimum values) - For each binary search iteration, the
checkfunction 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
checkfunction only uses a constant amount of extra space for variablescntandi - 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. Using Wrong Binary Search Template
Incorrect:
while left < right: mid = (left + right) // 2 if feasible(mid): right = mid else: left = mid + 1 return left
Correct (template-compliant):
first_true_index = -1 while left <= right: mid = (left + right) // 2 if feasible(mid): first_true_index = mid right = mid - 1 else: left = mid + 1 return first_true_index if first_true_index != -1 else 0
2. Not Sorting the Array First
The greedy approach only works on a sorted array. Without sorting, adjacent elements won't have minimal differences, and the pairing strategy fails.
3. Using Wrong Comparison in Validation Function
# WRONG: Using < instead of <= if nums[i + 1] - nums[i] < max_diff: # Should be <=
Using < would incorrectly reject valid pairs where the difference equals the threshold.
4. Checking for Exactly p Pairs Instead of At Least p Pairs
# WRONG: return pair_count == p # CORRECT: return pair_count >= p
Having more valid pairs than needed still means the threshold works.
5. Edge Case When p = 0
When p = 0, no pairs are needed, so the answer is 0. The template handles this since feasible(0) returns True when p = 0.
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
https assets algo monster cover_photos Binary_Search svg Binary Search Intuition Binary search is an efficient array search algorithm It works by narrowing down the search range by half each time If you have looked up a word in a physical dictionary you've already used binary search in real life Let's
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!