Minimize the Maximum Adjacent Element Difference
HardPrompt
You are given an array of integers nums. Some values in nums are missing and are denoted by -1.
You must choose a pair of positive integers (x, y) exactly once and replace each missing element with either x or y.
You need to minimize the maximum absolute difference between adjacent elements of nums after replacements.
Return the minimum possible difference.
Example 1:
Input: nums = [1,2,-1,10,8]
Output: 4
Explanation:
By choosing the pair as (6, 7), nums can be changed to [1, 2, 6, 10, 8].
The absolute differences between adjacent elements are:
|1 - 2| == 1|2 - 6| == 4|6 - 10| == 4|10 - 8| == 2
Example 2:
Input: nums = [-1,-1,-1]
Output: 0
Explanation:
By choosing the pair as (4, 4), nums can be changed to [4, 4, 4].
Example 3:
Input: nums = [-1,10,-1,8]
Output: 1
Explanation:
By choosing the pair as (11, 9), nums can be changed to [11, 10, 9, 8].
Constraints:
2 <= nums.length <= 105nums[i]is either -1 or in the range[1, 109].
Approaches
2 approaches with complexity analysis and trade-offs.
This approach uses binary search on the answer. The core of the problem is to create a check(d) function to determine if a given maximum difference d is achievable. This function first determines the valid range for each missing value by propagating constraints from its neighbors. Then, it solves the resulting "two-stabber" problem: finding two points x and y (with |x-y| <= d) that intersect all the required value ranges. This version of check(d) solves the two-stabber problem by iterating through all potential stabbing points, leading to a quadratic time complexity.
Algorithm
- The main idea is to binary search on the answer, which is the minimum possible maximum adjacent difference, let's call it
d. - The range for binary search would be from
0to10^9. - For each
din the binary search, we need a functioncheck(d)that verifies if it's possible to choose(x, y)such that the maximum adjacent difference is at mostd. - The
check(d)function first calculates the possible range of values for each missing element. This is done by propagating constraints from neighbors.- Initialize a range
[min_val, max_val]for each element. For non-missing elementsv, the range is[v, v]. For missing elements, it's a wide range like[1, 2*10^9]. - Perform a left-to-right pass. For each element
i, its range is updated by intersecting its current range with[range[i-1].min - d, range[i-1].max + d]. - Perform a right-to-left pass, updating
range[i]based onrange[i+1]similarly. - If any range becomes invalid (
min > max),dis not possible.
- Initialize a range
- After propagation, we have a set of required intervals for all missing elements. We need to find if there exist two numbers
xandywith|x - y| <= dthat can "stab" all these intervals (i.e., for each interval, at least one ofxoryis inside it). - This subproblem can be solved by trying out candidate values for
x. The endpoints of the required intervals are good candidates. For each candidatex, we find all intervals not stabbed byx. We then compute the intersection of these unstabbed intervals. If this intersection is non-empty, say[L, R], we check if we can pick ayfrom[L, R]such that|x - y| <= d. This means checking if[L, R]and[x-d, x+d]overlap. - Since there can be
O(N)intervals and for each candidatexwe check all other intervals, this check takesO(N^2)time.
Walkthrough
The problem asks to minimize a maximum value, which strongly suggests binary searching on the answer. Let the answer be d. Our goal is to find the smallest d for which we can replace the -1s and satisfy the condition.
The check(d) function works as follows:
- Base Case Check: First, iterate through the array and check if any pair of adjacent non-
-1elementsnums[i]andnums[i+1]has a difference greater thand. If so,dis impossible, andcheck(d)returnsfalse. - Constraint Propagation: We determine the valid range of values for each
-1. An elegant way to do this is by propagating constraints. We maintain arange[i] = [min_val, max_val]for each positioni.- For
nums[i] != -1,range[i] = [nums[i], nums[i]]. - For
nums[i] == -1, we initializerange[i]to a very large range, e.g.,[1, 2*10^9]. - We then do two passes:
- Left-to-right: For
ifrom 1 ton-1,range[i]is constrained byrange[i-1]. Any value inrange[i]must be at mostdaway from some value inrange[i-1]. This means the newrange[i]is the intersection of the oldrange[i]and[range[i-1].min - d, range[i-1].max + d]. - Right-to-left: A similar pass from
n-2down to0further tightens the ranges.
- Left-to-right: For
- If at any point a range becomes empty (
min > max),dis impossible.
- For
- Two-Stabber Problem (O(N^2) solution): After propagation, we have a list of
mrequired intervals for themmissing numbers. We need to find if there existx, ywith|x-y| <= dthat stab all these intervals. We can test this by picking a candidate forxfrom the endpoints of the intervals. For each candidatex:- Identify all intervals not stabbed by
x. - Calculate the intersection of these unstabbed intervals.
- If this intersection
[L, R]is valid, check if it overlaps with[x-d, x+d]. If it does, a validyexists, andcheck(d)istrue. - If we exhaust all candidates for
xwithout success,check(d)isfalse.
- Identify all intervals not stabbed by
This check function is O(N^2) because of the nested loops in the two-stabber part. The overall complexity is O(N^2 * log(MAX_VAL)).
// The main binary search structure would call this check function.private boolean checkSlow(int d, int[] nums) { int n = nums.length; // Steps 1 & 2: Constraint Propagation (O(N)) // ... (This part is identical to the optimal approach's check function) long[][] ranges = new long[n][2]; // ... initialization and propagation logic ... // Step 3: Collect intervals for -1s List<long[]> requiredIntervals = new ArrayList<>(); for (int i = 0; i < n; i++) { if (nums[i] == -1) { if (ranges[i][0] > ranges[i][1]) return false; // Check from propagation requiredIntervals.add(ranges[i]); } } if (requiredIntervals.isEmpty()) { return true; } // O(N^2) check for two-stabber problem for (long[] p1 : requiredIntervals) { // Candidate 1 for x: left endpoint if (canStabWith(p1[0], d, requiredIntervals)) return true; // Candidate 2 for x: right endpoint if (canStabWith(p1[1], d, requiredIntervals)) return true; } return false;} private boolean canStabWith(long x, int d, List<long[]> intervals) { long[] intersection = {1L, 2_000_000_000L}; for (long[] p : intervals) { if (x < p[0] || x > p[1]) { // If x doesn't stab p intersection[0] = Math.max(intersection[0], p[0]); intersection[1] = Math.min(intersection[1], p[1]); } } if (intersection[0] > intersection[1]) { // No common intersection for y // This means all intervals were stabbed by x return true; } // Check if intersection for y overlaps with [x-d, x+d] long y_min = Math.max(intersection[0], x - d); long y_max = Math.min(intersection[1], x + d); return y_min <= y_max;}Complexity
Time
O(N^2 * log(MAX_VAL)), where N is the length of `nums` and MAX_VAL is the upper bound of the search space (10^9). The `check` function takes O(N^2) and it's called O(log(MAX_VAL)) times.
Space
O(N) to store the ranges for each element.
Trade-offs
Pros
The binary search framework is correct for this type of minimization problem.
The constraint propagation logic correctly narrows down the possibilities for replacement values.
Cons
The
check(d)function has a time complexity ofO(N^2), which is too slow given the constraints (N <= 10^5).The overall time complexity of
O(N^2 * log(MAX_VAL))will likely result in a Time Limit Exceeded error.
This is the optimal approach, which also employs binary search on the answer d. It features a much more efficient check(d) function. The constraint propagation part remains the same. The improvement lies in solving the two-stabber problem in O(N log N) time. Instead of testing individual points, we analyze the structure of the required intervals. By sorting the intervals and merging them, we can find the connected components of their union. A valid solution with two points x and y exists if and only if there are at most two such components and, in the case of two, their separation is no more than d.
Algorithm
- This approach also uses binary search on the answer
d. - The
check(d)function is optimized. The initial steps of checking fixed neighbors and propagating constraints are the same as the previous approach and takeO(N)time. - The key improvement is in solving the two-stabber subproblem more efficiently.
- After getting the list of
mrequired intervals for the-1s, we analyze their geometric structure. - The core idea is that a set of intervals can be stabbed by two points only if the union of these intervals forms at most two connected components.
- To find the connected components:
- Sort the
mintervals based on their start points. - Iterate through the sorted intervals and merge any that overlap or are adjacent. This process yields the connected components of the union.
- Sort the
- After finding the components:
- If there are 0 or 1 components, all intervals can be stabbed by a single point (e.g.,
x=y), sodis achievable. Returntrue. - If there are more than 2 components, it's impossible to stab them all with two points. Return
false. - If there are exactly 2 components, say
[L1, R1]and[L2, R2](withR1 < L2), we must pick one stabbing point from each. This is possible if the distance between them is at mostd. We check ifL2 - R1 <= d. If it is, returntrue; otherwise,false.
- If there are 0 or 1 components, all intervals can be stabbed by a single point (e.g.,
- This check, dominated by sorting, takes
O(m log m)orO(N log N)time.
Walkthrough
This approach refines the check(d) function to achieve a better time complexity, making it feasible for the given constraints. The overall structure of binary searching on the answer d remains.
Optimized check(d) function:
The first two steps (checking fixed neighbors and constraint propagation) are identical to the previous approach and run in O(N) time.
1. & 2. Constraint Propagation: Same as before. We generate the final required intervals for all -1 positions.
3. Efficient Two-Stabber Solution (O(N log N)):
Let S be the list of m required intervals. The problem is to find if x, y exist with |x-y| <= d that stab all intervals in S.
- Key Insight: If a set of intervals can be stabbed by two points, their union cannot have more than two disjoint parts (connected components). If there were three, say
C1, C2, C3, we would need at least three points to stab one interval from each. - Algorithm:
a. Collect the list of
mrequired intervals from the propagation step. b. If the list is empty, returntrue. c. Sort the intervals by their starting points. This costsO(m log m). d. Find the connected components by merging overlapping/adjacent intervals. This can be done in a single pass (O(m)) over the sorted intervals. e. Analyze the number of components: * If there is 1 or 0 component, it means all intervals are connected (or there are no intervals). A single point can stab them all. We can choosex=y, so|x-y|=0 <= d. Returntrue. * If there are more than 2 components, it's impossible. Returnfalse. * If there are exactly 2 components,[L1, R1]and[L2, R2], we must pick one point from each component. To satisfy|x-y| <= d, the minimum distance between the components must be at mostd. The distance isL2 - R1(assuming sorted components). So, we returntrueifL2 - R1 <= d, andfalseotherwise.
This optimized check runs in O(N log N) time, dominated by sorting the intervals. The total time complexity of the solution is O(N log N * log(MAX_VAL)), which is efficient enough.
public int minimizeMaxDifference(int[] nums) { int low = 0, high = 1_000_000_000, ans = high; while (low <= high) { int mid = low + (high - low) / 2; if (check(mid, nums)) { ans = mid; high = mid - 1; } else { low = mid + 1; } } return ans;} private boolean check(int d, int[] nums) { int n = nums.length; for (int i = 0; i < n - 1; i++) { if (nums[i] != -1 && nums[i + 1] != -1 && Math.abs(nums[i] - nums[i + 1]) > d) { return false; } } long[][] ranges = new long[n][2]; for (int i = 0; i < n; i++) { if (nums[i] != -1) { ranges[i][0] = ranges[i][1] = nums[i]; } else { ranges[i][0] = 1; ranges[i][1] = 2_000_000_000L; // Large enough positive range } } for (int i = 1; i < n; i++) { ranges[i][0] = Math.max(ranges[i][0], ranges[i - 1][0] - d); ranges[i][1] = Math.min(ranges[i][1], ranges[i - 1][1] + d); if (ranges[i][0] > ranges[i][1]) return false; } for (int i = n - 2; i >= 0; i--) { ranges[i][0] = Math.max(ranges[i][0], ranges[i + 1][0] - d); ranges[i][1] = Math.min(ranges[i][1], ranges[i + 1][1] + d); if (ranges[i][0] > ranges[i][1]) return false; } List<long[]> requiredIntervals = new ArrayList<>(); for (int i = 0; i < n; i++) { if (nums[i] == -1) { requiredIntervals.add(ranges[i]); } } if (requiredIntervals.isEmpty()) return true; requiredIntervals.sort(Comparator.comparingLong(a -> a[0])); List<long[]> components = new ArrayList<>(); if (!requiredIntervals.isEmpty()) { components.add(requiredIntervals.get(0).clone()); for (int i = 1; i < requiredIntervals.size(); i++) { long[] current = requiredIntervals.get(i); long[] lastComp = components.get(components.size() - 1); if (current[0] <= lastComp[1]) { // Merge overlapping intervals lastComp[1] = Math.max(lastComp[1], current[1]); } else { components.add(current.clone()); } } } if (components.size() > 2) return false; if (components.size() <= 1) return true; return components.get(1)[0] - components.get(0)[1] <= d;}Complexity
Time
O(N log N * log(MAX_VAL)). The `check` function is dominated by sorting the `m` intervals (where `m <= N`), which takes `O(m log m)`. This is called `O(log(MAX_VAL))` times by the binary search.
Space
O(N) to store the ranges and the list of required intervals.
Trade-offs
Pros
This approach is highly efficient and passes within the given time limits.
It correctly models the problem and breaks it down into standard subproblems (binary search, interval manipulation).
The logic for solving the two-stabber problem by analyzing connected components is robust.
Cons
The logic is more complex to implement correctly compared to the naive check.
Requires careful handling of edge cases and interval manipulations.
Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.