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 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
31
1class 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}
33
1class 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};
35
1function 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}
31

Solution 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 = 0 and right = nums[-1] - nums[0]
  • Track the answer with first_true_index = -1
  • Use while left <= right loop structure
  • When feasible(mid) returns True, update first_true_index = mid and search left with right = mid - 1
  • When feasible(mid) returns False, search right with left = 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 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.

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. 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.

Loading...
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