2616. Minimize the Maximum Difference of Pairs - Explanation

Problem Link

Description

You are given a 0-indexed integer array nums and an integer p. Find p pairs of indices of nums such that the maximum difference amongst all the pairs is minimized. Also, ensure no index appears more than once amongst the p pairs.

Note that for a pair of elements at the index i and j, the difference of this pair is |nums[i] - nums[j]|, where |x| represents the absolute value of x.

Return the minimum maximum difference among all p pairs. We define the maximum of an empty set to be zero.

Example 1:

Input: nums = [10,1,2,7,1,3], p = 2

Output: 1

Explanation: The first pair is formed from the indices 1 and 4, and the second pair is formed from the indices 2 and 5.
The maximum difference is max(|nums[1] - nums[4]|, |nums[2] - nums[5]|) = max(0, 1) = 1. Therefore, we return 1.

Example 2:

Input: nums = [4,2,1,2], p = 1

Output: 0

Explanation: Let the indices 1 and 3 form a pair. The difference of that pair is |2 - 2| = 0, which is the minimum we can attain.

Constraints:

  • 1 <= nums.length <= 100,000
  • 1 <= nums[i] <= 1,000,000,000
  • 0 <= p <= (nums.length)/2


Topics

Company Tags

Please upgrade to NeetCode Pro to view company tags.



Prerequisites

Before attempting this problem, you should be comfortable with:

  • Sorting - Optimal pairs are adjacent elements in sorted order, making sorting essential for the greedy approach
  • Dynamic Programming (Memoization) - The take/skip decision pattern with overlapping subproblems requires DP for efficient solutions
  • Binary Search - The optimal solution uses binary search on the answer space to find the minimum valid threshold
  • Greedy Algorithms - Understanding when greedy choices (pairing adjacent elements) lead to optimal solutions

1. Greedy + Dynamic Programming (Top-Down)

Intuition

To minimize the maximum difference among p pairs, we first sort the array. After sorting, optimal pairs are always adjacent elements because non-adjacent pairs would have larger differences. The problem becomes selecting p non-overlapping adjacent pairs to minimize the largest difference.

This is a classic DP problem: at each position, we decide whether to pair the current element with the next one (taking them both) or skip the current element. The goal is to minimize the maximum difference among all selected pairs.

Algorithm

  1. Sort the array.
  2. Define dfs(i, pairs) as the minimum possible maximum difference when considering elements from index i onward and needing pairs more pairs.
  3. Base cases:
    • If pairs == p, return 0 (no more pairs needed).
    • If i >= n - 1, return infinity (cannot form more pairs).
  4. At each position, choose the better option:
    • Take: Pair elements at i and i+1, recursively solve for i+2 with one fewer pair needed. The result is the max of this pair's difference and the recursive result.
    • Skip: Move to i+1 without pairing.
  5. Return the min of take and skip.
class Solution:
    def minimizeMax(self, nums: List[int], p: int) -> int:
        n = len(nums)
        nums.sort()
        dp = {}

        def dfs(i, pairs):
            if pairs == p:
                return 0
            if i >= n - 1:
                return float('inf')
            if (i, pairs) in dp:
                return dp[(i, pairs)]

            take = max(nums[i + 1] - nums[i], dfs(i + 2, pairs + 1))
            skip = dfs(i + 1, pairs)
            dp[(i, pairs)] = min(take, skip)
            return dp[(i, pairs)]

        return dfs(0, 0)

Time & Space Complexity

  • Time complexity: O(np)O(n * p)
  • Space complexity: O(np)O(n * p)

Where nn is the size of the input array and pp is the number of pairs to select.


2. Greedy + Dynamic Programming (Bottom-Up)

Intuition

The same logic as the top-down approach, but we fill the DP table iteratively from the end of the array backward. At each position and pair count, we compute the minimum maximum difference achievable.

Algorithm

  1. Sort the array.
  2. Create a 2D DP table where dp[i][pairs] represents the minimum maximum difference starting from index i with pairs pairs still needed.
  3. Initialize dp[i][0] = 0 for all i (no pairs needed means 0 difference).
  4. Fill the table from i = n - 2 down to 0:
    • For each number of pairs from 1 to p:
      • Take: max(nums[i+1] - nums[i], dp[i+2][pairs-1])
      • Skip: dp[i+1][pairs]
      • Store the min of take and skip.
  5. Return dp[0][p].
class Solution:
    def minimizeMax(self, nums: List[int], p: int) -> int:
        n = len(nums)
        nums.sort()

        dp = [[float('inf')] * (p + 1) for _ in range(n + 1)]
        for i in range(n + 1):
            dp[i][0] = 0

        for i in range(n - 2, -1, -1):
            for pairs in range(1, p + 1):
                take = float('inf')
                if i + 1 < n:
                    take = max(nums[i + 1] - nums[i], dp[i + 2][pairs - 1])

                skip = dp[i + 1][pairs]
                dp[i][pairs] = min(take, skip)

        return dp[0][p]

Time & Space Complexity

  • Time complexity: O(np)O(n * p)
  • Space complexity: O(np)O(n * p)

Where nn is the size of the input array and pp is the number of pairs to select.


3. Greedy + Dynamic Programming (Space Optimized)

Intuition

Since each row of the DP table only depends on the next two rows, we can reduce space by keeping only three 1D arrays instead of the full 2D table. We rotate these arrays as we iterate backward through the array.

Algorithm

  1. Sort the array.
  2. Use three arrays dp, dp1, and dp2 of size p + 1, all initialized to infinity except index 0 which is 0.
  3. Iterate from i = n - 1 down to 0:
    • For each pairs from 1 to p:
      • Take: max(nums[i+1] - nums[i], dp2[pairs-1]) if i + 1 < n
      • Skip: dp1[pairs]
      • dp[pairs] = min(take, skip)
    • Rotate: dp2 = dp1, dp1 = dp, reset dp.
  4. Return dp1[p].
class Solution:
    def minimizeMax(self, nums: List[int], p: int) -> int:
        n = len(nums)
        nums.sort()

        dp = [float('inf')] * (p + 1)
        dp1 = [float('inf')] * (p + 1)
        dp2 = [float('inf')] * (p + 1)

        dp[0] = dp1[0] = dp2[0] = 0
        for i in range(n - 1, -1, -1):
            for pairs in range(1, p + 1):
                take = float('inf')
                if i + 1 < n:
                    take = max(nums[i + 1] - nums[i], dp2[pairs - 1])
                skip = dp1[pairs]
                dp[pairs] = min(take, skip)

            dp2 = dp1[:]
            dp1 = dp[:]
            dp = [float('inf')] * (p + 1)
            dp[0] = 0

        return dp1[p]

Time & Space Complexity

  • Time complexity: O(np)O(n * p)
  • Space complexity: O(p)O(p)

Where nn is the size of the input array and pp is the number of pairs to select.


Intuition

Instead of DP, we can binary search on the answer. Given a threshold t, we greedily check if we can form p pairs where each pair has difference at most t. After sorting, we scan left to right: whenever two adjacent elements have difference at most t, we pair them and skip both. This greedy approach works because pairing earlier elements never blocks better options later.

The answer lies between 0 and max - min of the sorted array, so we binary search to find the smallest threshold that allows forming p pairs.

Algorithm

  1. Handle edge case: if p == 0, return 0.
  2. Sort the array.
  3. Binary search on threshold between 0 and nums[n-1] - nums[0]:
    • For each threshold mid, check if we can greedily form p pairs:
      • Scan the sorted array. If nums[i+1] - nums[i] <= mid, count a pair and skip to i+2. Otherwise move to i+1.
      • If count reaches p, the threshold is valid.
    • If valid, try a smaller threshold. Otherwise try larger.
  4. Return the smallest valid threshold.
class Solution:
    def minimizeMax(self, nums: List[int], p: int) -> int:
        if p == 0:
            return 0

        def isValid(threshold):
            i, cnt = 0, 0
            while i < len(nums) - 1:
                if abs(nums[i] - nums[i + 1]) <= threshold:
                    cnt += 1
                    i += 2
                else:
                    i += 1
                if cnt == p:
                    return True
            return False

        nums.sort()
        l, r = 0, nums[-1] - nums[0]
        res = nums[-1] - nums[0]

        while l <= r:
            m = l + (r - l) // 2
            if isValid(m):
                res = m
                r = m - 1
            else:
                l = m + 1

        return res

Time & Space Complexity

  • Time complexity: O(nlogn+nlogm)O(n\log n + n\log m)
  • Space complexity: O(1)O(1) or O(n)O(n) depending on the sorting algorithm.

Where nn is the size of the input array and mm is the maximum value in the array.


Common Pitfalls

Forgetting to Sort the Array First

Optimal pairs in this problem are always adjacent elements in the sorted array. If you try to form pairs without sorting, you might pair elements that are far apart in value, leading to suboptimal or incorrect results. Always sort the array before applying either the DP or binary search approach.

Incorrect Greedy Pairing in Binary Search Validation

When validating a threshold in the binary search approach, you must skip both elements when forming a pair (move to i + 2), not just one. A common bug is incrementing by 1 after forming a pair, which would allow the same element to be used in multiple pairs. Remember: if nums[i] and nums[i+1] form a valid pair, jump to index i + 2 to ensure non-overlapping pairs.

Not Handling the Edge Case When p Equals Zero

When p = 0, no pairs need to be formed, so the answer is always 0 regardless of the array contents. Some solutions fail to handle this edge case and may return incorrect results or encounter errors when attempting to form zero pairs. Always check for p == 0 at the start and return 0 immediately.