A clear explanation of minimizing the array range after adding either +k or -k to every element.
Problem Restatement
We are given an integer array nums and an integer k.
For every index i, we must either:
nums[i] + kor:
nums[i] - kReturn the minimum possible difference between the maximum and minimum values after all choices are made.
Unlike the previous problem, every number must choose exactly one of the two operations.
Input and Output
| Item | Meaning |
|---|---|
| Input | Integer array nums and integer k |
| Output | Minimum possible range after modifications |
| Allowed operations | Add k or subtract k from every element |
| Goal | Minimize max(nums) - min(nums) |
Function shape:
def smallestRangeII(nums: list[int], k: int) -> int:
...Examples
Example 1:
nums = [1]
k = 0The array stays:
[1]Range:
1 - 1 = 0Answer:
0Example 2:
nums = [0, 10]
k = 2Possible transformation:
[2, 8]Range:
8 - 2 = 6Answer:
6Example 3:
nums = [1, 3, 6]
k = 3One optimal transformation is:
[4, 6, 3]Sorted result:
[3, 4, 6]Range:
6 - 3 = 3Answer:
3The official examples use these exact results. (leetcode.com)
First Thought: Try Every Combination
For each number, we have two choices:
+kor:
-kSo with n numbers, there are:
2^npossible arrays.
Trying all combinations is impossible for large n.
We need to exploit structure in the problem.
Key Insight
Sort the array first.
Suppose:
nums = [1, 3, 6]
k = 3After sorting, imagine splitting the array into two parts.
- Left part gets
+k - Right part gets
-k
For example:
[1] -> +3
[3, 6] -> -3becomes:
[4, 0, 3]The important insight is:
After sorting, there is always an optimal solution where all smaller elements use +k and all larger elements use -k.
We only need to try every split point.
What Can Become the Minimum or Maximum?
After sorting:
nums[0] <= nums[1] <= ... <= nums[n - 1]Suppose split position i means:
nums[0..i]use+knums[i+1..n-1]use-k
Then:
Largest possible value:
max(nums[i] + k, nums[n - 1] - k)Smallest possible value:
min(nums[0] + k, nums[i + 1] - k)So the range is:
high - lowWe try every split and take the minimum.
Algorithm
- Sort the array.
- Initialize the answer as the original range.
- For every split position
i:- Left side uses
+k - Right side uses
-k
- Left side uses
- Compute:
- New maximum
- New minimum
- Their difference
- Keep the smallest difference.
Correctness
Let the sorted array be:
nums[0] <= nums[1] <= ... <= nums[n - 1]Consider any valid assignment of +k and -k.
Suppose there exist indices a < b such that:
nums[a]uses-knums[b]uses+k
Since:
nums[a] <= nums[b]swapping their choices cannot increase the range:
- Increasing the larger value even more is not helpful.
- Decreasing the smaller value even more is not helpful.
By repeatedly applying this exchange argument, every optimal solution can be transformed into one where:
- Smaller values use
+k - Larger values use
-k
So an optimal solution always corresponds to some split point.
For a chosen split:
The maximum value must be either:
- the largest boosted left value
- or the largest reduced right value
The minimum value must be either:
- the smallest boosted left value
- or the smallest reduced right value
The algorithm checks all such splits and computes the resulting range exactly.
Therefore, it returns the minimum possible range.
Complexity
| Metric | Value | Why |
|---|---|---|
| Time | O(n log n) | Sorting dominates |
| Space | O(1) extra | Ignoring sorting internals |
Implementation
class Solution:
def smallestRangeII(self, nums: list[int], k: int) -> int:
nums.sort()
n = len(nums)
answer = nums[-1] - nums[0]
for i in range(n - 1):
high = max(nums[i] + k, nums[-1] - k)
low = min(nums[0] + k, nums[i + 1] - k)
answer = min(answer, high - low)
return answerCode Explanation
First sort the array:
nums.sort()Without sorting, we cannot reason about split positions.
The original range is one candidate answer:
answer = nums[-1] - nums[0]Now try every split:
for i in range(n - 1):Left side:
nums[0..i]gets +k.
Right side:
nums[i+1..n-1]gets -k.
The largest value becomes:
high = max(nums[i] + k, nums[-1] - k)The smallest value becomes:
low = min(nums[0] + k, nums[i + 1] - k)Update the best answer:
answer = min(answer, high - low)Testing
def run_tests():
s = Solution()
assert s.smallestRangeII([1], 0) == 0
assert s.smallestRangeII([0, 10], 2) == 6
assert s.smallestRangeII([1, 3, 6], 3) == 3
assert s.smallestRangeII([7, 8, 8], 5) == 1
assert s.smallestRangeII([2, 7, 2], 1) == 3
assert s.smallestRangeII([1, 10], 5) == 1
print("all tests passed")
run_tests()Test meaning:
| Test | Why |
|---|---|
[1], k = 0 | Single value |
[0, 10], k = 2 | Main sample |
[1, 3, 6], k = 3 | Mixed optimal split |
| Duplicate values | Checks equal elements |
| Unsorted input | Confirms sorting logic |
| Large adjustment | Range becomes very small |