2616. Minimize the Maximum Difference of Pairs - Explanation
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: 1Explanation: 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: 0Explanation: 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,0001 <= nums[i] <= 1,000,000,0000 <= p <= (nums.length)/2
Topics
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
- Sort the array.
- Define
dfs(i, pairs)as the minimum possible maximum difference when considering elements from indexionward and needingpairsmore pairs. - Base cases:
- If
pairs == p, return0(no more pairs needed). - If
i >= n - 1, return infinity (cannot form more pairs).
- If
- At each position, choose the better option:
- Take: Pair elements at
iandi+1, recursively solve fori+2with one fewer pair needed. The result is themaxof this pair's difference and the recursive result. - Skip: Move to
i+1without pairing.
- Take: Pair elements at
- Return the
minof 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:
- Space complexity:
Where is the size of the input array and 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
- Sort the array.
- Create a 2D DP table where
dp[i][pairs]represents the minimum maximum difference starting from indexiwithpairspairs still needed. - Initialize
dp[i][0] = 0for alli(no pairs needed means0difference). - Fill the table from
i = n - 2down to0:- For each number of pairs from
1top:- Take:
max(nums[i+1] - nums[i], dp[i+2][pairs-1]) - Skip:
dp[i+1][pairs] - Store the
minof take and skip.
- Take:
- For each number of pairs from
- 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:
- Space complexity:
Where is the size of the input array and 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
- Sort the array.
- Use three arrays
dp,dp1, anddp2of sizep + 1, all initialized to infinity except index0which is0. - Iterate from
i = n - 1down to0:- For each
pairsfrom1top:- Take:
max(nums[i+1] - nums[i], dp2[pairs-1])ifi + 1 < n - Skip:
dp1[pairs] dp[pairs] = min(take, skip)
- Take:
- Rotate:
dp2 = dp1,dp1 = dp, resetdp.
- For each
- 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:
- Space complexity:
Where is the size of the input array and is the number of pairs to select.
4. Greedy + Binary Search
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
- Handle edge case: if
p == 0, return0. - Sort the array.
- Binary search on threshold between
0andnums[n-1] - nums[0]:- For each threshold
mid, check if we can greedily formppairs:- Scan the sorted array. If
nums[i+1] - nums[i] <= mid, count a pair and skip toi+2. Otherwise move toi+1. - If count reaches
p, the threshold is valid.
- Scan the sorted array. If
- If valid, try a smaller threshold. Otherwise try larger.
- For each threshold
- 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 resTime & Space Complexity
- Time complexity:
- Space complexity: or depending on the sorting algorithm.
Where is the size of the input array and 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.
Sign in to join the discussion