Minimize the Heights II is a classic greedy array problem. Every tower must change by exactly k — you choose to either raise it or lower it — and you want the final array as flat as possible. The win comes from one observation: after sorting, the best plan is always "raise a prefix, lower the rest."
Problem. Given an array a of tower heights and an integer k, you must change every height
either up by k or down by k (no height may go below zero in the strict version). Minimize the
difference between the tallest and shortest tower after all the changes.
Example: a = [1, 5, 8, 10], k = 2. Raising 1 to 3 and lowering 8, 10 to 6, 8 gives
[3, 7, 6, 8], whose spread 8 − 3 = 5 is the smallest possible. Answer: 5.
The slow way first
Each tower independently goes up or down, so there are 2^n combinations of choices. Trying them all is O(2ⁿ) — hopeless for anything but a tiny array. We need structure.
The question to ask: if I knew which towers go up and which go down, would the assignment be arbitrary? It is not. Once the array is sorted, raising a short tower and lowering a tall one is what flattens the array. Raising a tall tower or lowering a short one only makes the spread worse.
The idea: sort, then split
Sort the array. Now imagine a split point i: every height in a[0..i] goes up by k, and every height in a[i+1..] goes down by k. Because the array was sorted, only two values can be the new tallest and only two can be the new shortest:
- new tallest =
max(a[i] + k, a[n-1] - k) - new shortest =
min(a[0] + k, a[i+1] - k)
Sweep i across every split, recompute that candidate spread, and keep the smallest one. Start the answer at the unmodified spread a[n-1] - a[0] so the "raise everything or lower everything" plan is covered too.
The key insight: sorting collapses 2^n choices into just n meaningful split points, because the four numbers that can be the new extremes are always the same handful of endpoints.
Walk through it
Step through the animation. The array is sorted to [1, 5, 8, 10] and k = 2. The pointer i marks the split. Boxes left of (and including) i are "raised"; the rest are "lowered." For each split we read off big and small, form the candidate spread, and shrink the running answer. The smallest spread we ever see is 5.
Pseudocode
sort a
ans = a[last] - a[first] # baseline: no useful split
for each split index i from 0 to n-2:
big = max(a[i] + k, a[last] - k) # tallest after raising left, lowering right
small = min(a[first] + k, a[i+1] - k) # shortest after the same split
ans = min(ans, big - small) # keep the flattest result so far
return ansThe Python solution
def get_min_diff(a, k):
a.sort()
n = len(a)
ans = a[n - 1] - a[0]
for i in range(n - 1):
big = max(a[i] + k, a[n - 1] - k)
small = min(a[0] + k, a[i + 1] - k)
ans = min(ans, big - small)
return ansa.sort()is what makes the prefix/suffix split valid — without it the endpoints mean nothing.ans = a[n - 1] - a[0]seeds the answer with the do-nothing-useful spread.- The loop runs
ifrom0ton - 2, soa[i + 1]is always in bounds. - Line 6 picks the tallest after the split: either the raised
a[i]or the lowereda[n - 1]. - Line 7 picks the shortest: either the raised
a[0]or the lowereda[i + 1]. - Line 8 keeps the smallest spread seen so far. After the sweep,
ansis the answer.
Complexity
| Case | Time | Notes |
|---|---|---|
| Brute force (every up/down choice) | O(2ⁿ) (moderate) | exponential, infeasible |
| Sort + single sweep (this solution) | O(n log n) (moderate) | sort dominates the O(n) sweep |
O(1) (fast)Sorting costs O(n log n) and the sweep is O(n), so the sort dominates. We use only a few scalar variables, so extra space is O(1) (ignoring the sort).
When this pattern shows up
When a problem forces a fixed change on every element and asks you to minimize a range or spread, try sorting first. Sorting often turns an exponential "which subset" question into a linear "where do I split the sorted line" question — the same move powers many greedy interval and partition problems.
Two easy mistakes: seed ans with the original spread (do not start at infinity and forget the
no-split case), and stop the loop at n - 2 so a[i + 1] never runs off the end. In the strict
variant, also skip any split where lowering would push a height below zero.
Practice
For sorted a = [1, 5, 8, 10] and k = 2, take the split i = 0. What are big and small, and what spread does it give?
1. Why must the array be sorted before the sweep?
2. At split index i, what is the new tallest tower?
3. Why do we seed ans with a[n-1] - a[0] before the loop?
4. What is the overall time complexity?