A clear explanation of solving Minimum Increment to Make Array Unique by sorting and greedily assigning the next available value.
Problem Restatement
We are given an integer array:
numsIn one move, we may choose any index and increment that value by 1.
We need to return the minimum number of moves required to make every value in nums unique.
The problem guarantees that the answer fits in a 32-bit integer. The official examples include [1,2,2] -> 1 and [3,2,1,2,1,7] -> 6.
Input and Output
| Item | Meaning |
|---|---|
| Input | Integer array nums |
| Output | Minimum number of increment moves |
| Operation | Increase one element by 1 |
| Goal | All values become unique |
| Constraint | 1 <= nums.length <= 10^5 |
| Constraint | 0 <= nums[i] <= 10^5 |
Function shape:
class Solution:
def minIncrementForUnique(self, nums: list[int]) -> int:
...Examples
Example 1:
nums = [1, 2, 2]The value 2 appears twice.
Increment one of them:
[1, 2, 2] -> [1, 2, 3]This takes 1 move.
Answer:
1Example 2:
nums = [3, 2, 1, 2, 1, 7]Sort it:
[1, 1, 2, 2, 3, 7]Make each value at least one more than the previous value:
[1, 1, 2, 2, 3, 7]
[1, 2, 3, 4, 5, 7]Moves:
0 + 1 + 1 + 2 + 2 + 0 = 6Answer:
6First Thought
A direct approach is to repeatedly find duplicates and increment one duplicate until it reaches an unused value.
This works, but it can do too much repeated searching.
For example:
nums = [1, 1, 1, 1]We need to turn it into:
[1, 2, 3, 4]If we handle duplicates one at a time without order, we may repeatedly check values that are already occupied.
Sorting makes the needed final values clear.
Key Insight
After sorting, we should process numbers from smallest to largest.
For each number, choose the smallest possible final value that keeps it unique.
Suppose the previous final value is:
prevThen the current final value must be at least:
prev + 1If the current number x is already larger than prev, we keep it.
If x <= prev, we increment it to prev + 1.
This greedy choice is optimal because assigning the current number to the smallest available valid value leaves as much room as possible for later numbers.
Algorithm
Sort nums.
Initialize:
moves = 0
prev = -1For each number x in sorted order:
- The smallest allowed final value is
prev + 1. - Set:
new_value = max(x, prev + 1)- Add the increment amount:
moves += new_value - x- Update:
prev = new_valueReturn moves.
Walkthrough
Use:
nums = [3, 2, 1, 2, 1, 7]Sort:
[1, 1, 2, 2, 3, 7]Original x | Smallest allowed | Final value | Moves added |
|---|---|---|---|
| 1 | 0 | 1 | 0 |
| 1 | 2 | 2 | 1 |
| 2 | 3 | 3 | 1 |
| 2 | 4 | 4 | 2 |
| 3 | 5 | 5 | 2 |
| 7 | 6 | 7 | 0 |
Total moves:
6Correctness
After sorting, consider the numbers from smallest to largest.
For the first number, keeping it unchanged is always optimal because there is no earlier value to conflict with.
For every later number x, all previous final values are already fixed and unique. Let the largest previous final value be prev. To keep uniqueness while preserving sorted order, the current final value must be at least prev + 1.
The algorithm assigns:
max(x, prev + 1)This is the smallest value that is both reachable by increments and unique relative to previous values.
Any valid solution must assign the current number to at least this value. Assigning it to anything larger would add extra moves and would not help previous numbers. It would also make later numbers no easier to place.
Therefore, the greedy choice is optimal for each sorted position.
By induction, after processing every number, the algorithm has built a unique array with the minimum total number of increments.
Complexity
Let:
n = len(nums)| Metric | Value | Why |
|---|---|---|
| Time | O(n log n) | Sorting dominates |
| Space | O(1) or O(n) | Depends on the sorting implementation |
The greedy scan after sorting is O(n).
Implementation
class Solution:
def minIncrementForUnique(self, nums: list[int]) -> int:
nums.sort()
moves = 0
prev = -1
for x in nums:
new_value = max(x, prev + 1)
moves += new_value - x
prev = new_value
return movesCode Explanation
Sort the array first:
nums.sort()This groups duplicates and lets us assign final values from left to right.
prev stores the previous final value:
prev = -1For each x, the final value must be at least prev + 1:
new_value = max(x, prev + 1)The number of moves for this element is:
new_value - xThen we update the previous final value:
prev = new_valueTesting
def run_tests():
s = Solution()
assert s.minIncrementForUnique([1, 2, 2]) == 1
assert s.minIncrementForUnique([3, 2, 1, 2, 1, 7]) == 6
assert s.minIncrementForUnique([1, 1, 1, 1]) == 6
assert s.minIncrementForUnique([0]) == 0
assert s.minIncrementForUnique([0, 0]) == 1
assert s.minIncrementForUnique([0, 2, 2, 2]) == 3
assert s.minIncrementForUnique([5, 5, 5, 5, 5]) == 10
print("all tests passed")
run_tests()| Test | Why |
|---|---|
[1,2,2] | Official basic case |
[3,2,1,2,1,7] | Multiple duplicates |
[1,1,1,1] | All values equal |
[0] | Single element |
[0,0] | Small duplicate case |
[0,2,2,2] | Duplicate group with gap |
[5,5,5,5,5] | Repeated same value |