Facebook Pixel

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:

  1. The maximum difference among all the pairs is as small as possible (minimized)
  2. 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 form p = 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. This pair has an acceptable difference (≤ diff)
  2. Using nums[i] now with its closest neighbor nums[i+1] is optimal - any other pairing for nums[i] would likely result in a larger difference
  3. Once we use indices i and i+1, we skip to i+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 if nums[i+1] - nums[i] <= diff
  • If yes: form the pair (i, i+1), increment pair count, and jump to i+2
  • If no: skip i and try from i+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:

  1. Adjacent elements have the smallest differences
  2. If we skip pairing nums[i] with nums[i+1] when their difference is acceptable, any future pairing of nums[i] will likely have a larger difference
  3. 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 Evaluator

Example 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
  • 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
  • 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
  • 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, where n is the length of nums
  • Binary search using bisect_left performs O(log m) iterations, where m = nums[-1] - nums[0] (the difference between maximum and minimum values)
  • For each binary search iteration, the check function is called, which takes O(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 variables cnt and i
  • 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.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

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

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More