LeetCode 2009 - Minimum Number of Operations to Make Array Continuous
The problem asks us to transform a given integer array nums into a continuous array with the minimum number of operations.
Difficulty: 🔴 Hard
Topics: Array, Hash Table, Binary Search, Sliding Window
Solution
Problem Understanding
The problem asks us to transform a given integer array nums into a continuous array with the minimum number of operations. A continuous array is defined by two conditions: all elements are unique, and the difference between the maximum and minimum element equals the length of the array minus one. Essentially, a continuous array is a sequence of consecutive integers without duplicates.
The input nums can contain up to 10^5 elements, and each element can be as large as 10^9. This tells us that naive solutions that iterate over all possible sequences or perform O(n^2) comparisons are not feasible due to time constraints. The expected output is the minimum number of replacements needed to make the array continuous. Edge cases include arrays that are already continuous, arrays with all duplicates, arrays with very large gaps between numbers, and arrays of length 1.
Approaches
Brute Force
A brute-force solution would consider every possible continuous sequence of length n (where n is the length of nums) and count how many numbers need to be replaced to match this sequence. While correct, this is extremely inefficient because there are potentially O(max(nums)) such sequences to check, which is infeasible for large numbers.
Key Insight for Optimal Solution
The main observation is that we do not need to consider every possible sequence explicitly. Instead, we only need to focus on unique elements and use a sliding window over the sorted unique array. For any subarray of length k where the difference between the max and min is less than n, the number of operations required is n - k. The goal is to maximize k (the number of elements already fitting in a consecutive sequence) to minimize operations. This approach leverages sorting and the two-pointer sliding window technique, making it efficient.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O((max(nums) - min(nums)) * n) | O(n) | Check all possible sequences explicitly, too slow for large input |
| Optimal | O(n log n) | O(n) | Sort unique elements and use sliding window to find longest consecutive subarray |
Algorithm Walkthrough
- Remove duplicates from
numsto focus on unique elements, because duplicates must be replaced anyway. - Sort the unique elements to bring consecutive elements together, making it easier to find the largest consecutive subsequence.
- Initialize two pointers,
leftandright, for a sliding window. This window will represent a candidate consecutive sequence. - Iterate with
rightpointer through the sorted array. For each position, moveleftforward until the difference betweensorted[right]andsorted[left]is less thann. - The number of elements in the current window is
window_size = right - left + 1. The number of operations required isn - window_size. - Keep track of the minimum operations across all windows.
- Return the minimum operations.
Why it works: By focusing on the longest consecutive subsequence in the sorted unique array, we are effectively minimizing the number of replacements needed. Since any element outside this window must be replaced, maximizing the window size directly minimizes operations.
Python Solution
from typing import List
class Solution:
def minOperations(self, nums: List[int]) -> int:
n = len(nums)
unique_nums = sorted(set(nums))
min_ops = n
left = 0
for right in range(len(unique_nums)):
while unique_nums[right] - unique_nums[left] >= n:
left += 1
window_size = right - left + 1
min_ops = min(min_ops, n - window_size)
return min_ops
Explanation: We first deduplicate and sort nums. Then we use a sliding window to check the longest consecutive subsequence possible under the continuous array definition. For each window, we calculate how many elements would need to be replaced (n - window_size) and track the minimum. The solution is efficient due to sorting and the linear two-pointer scan.
Go Solution
import (
"sort"
)
func minOperations(nums []int) int {
n := len(nums)
uniqueMap := make(map[int]bool)
for _, num := range nums {
uniqueMap[num] = true
}
uniqueNums := make([]int, 0, len(uniqueMap))
for num := range uniqueMap {
uniqueNums = append(uniqueNums, num)
}
sort.Ints(uniqueNums)
minOps := n
left := 0
for right := 0; right < len(uniqueNums); right++ {
for uniqueNums[right]-uniqueNums[left] >= n {
left++
}
windowSize := right - left + 1
if n-windowSize < minOps {
minOps = n - windowSize
}
}
return minOps
}
Explanation: Go requires handling deduplication manually using a map. The sorting and sliding window logic is similar to Python. The solution carefully handles integer differences to avoid overflow, which is straightforward in Go since inputs are within the int range.
Worked Examples
Example 1
nums = [4,2,5,3]
unique_nums = [2,3,4,5]
window size = 4, n - window_size = 0
Output = 0
Example 2
nums = [1,2,3,5,6]
unique_nums = [1,2,3,5,6]
Window candidates:
left=0, right=2 -> window size=3, operations=5-3=2
left=0, right=3 -> 5-1 >=5, left++ -> window 2-3, window_size=2
left=1, right=3 -> 5-2=3<5, window_size=3, operations=2
left=2, right=4 -> 6-3=3<5, window_size=3, operations=2
Optimal window_size=4? Actually 1 operation needed by replacing 6->4
Check sliding window carefully. Correct window 1-3: size=4, operations=1
Output = 1
Example 3
nums = [1,10,100,1000]
unique_nums = [1,10,100,1000]
All windows smaller than n=4
Longest window_size=1
Operations=4-1=3
Output = 3
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n log n) | Sorting the unique array dominates; sliding window is O(n) |
| Space | O(n) | To store unique elements |
Sorting dominates the time complexity. The sliding window ensures we only scan each element once after sorting, giving an efficient linear pass.
Test Cases
# Provided examples
assert Solution().minOperations([4,2,5,3]) == 0 # already continuous
assert Solution().minOperations([1,2,3,5,6]) == 1 # replace 6 -> 4
assert Solution().minOperations([1,10,100,1000]) == 3 # replace 10, 100, 1000 -> 2,3,4
# Edge cases
assert Solution().minOperations([1]) == 0 # single element
assert Solution().minOperations([1,1,1,1]) == 3 # all duplicates
assert Solution().minOperations([1,3,2,5,4,6]) == 0 # already continuous with shuffled order
assert Solution().minOperations([1000000000,1]) == 1 # large gap
assert Solution().minOperations(list(range(1,100001))) == 0 # large continuous array
| Test | Why |
|---|---|
| [4,2,5,3] | Already continuous, minimal operation check |
| [1,2,3,5,6] | Small gap requiring single replacement |
| [1,10,100,1000] | Large gaps, maximum replacements needed |
| [1] | Single element, edge case |
| [1,1,1,1] | All duplicates, need n-1 replacements |
| [1,3,2,5,4,6] | Already continuous but unordered |
| [1000000000,1] | Very large gap between elements |
| large continuous array | Performance and scalability check |
Edge Cases
Single element array: With only one number, the array is trivially continuous, so no operation is needed. The code handles this correctly because the sliding window size equals n.
All duplicates: Arrays where every element is the same require replacing all but one element. Deduplication ensures the sliding window starts with a single unique element, resulting in n - 1 operations.
Large gaps: Elements separated by large values can appear to be continuous if miscalculated. Sorting ensures that differences are calculated correctly and the sliding window only considers feasible sequences. The algorithm handles this naturally without overflow, given input constraints.