2009. Minimum Number of Operations to Make Array Continuous
Problem Description
You have an integer array nums
. You can perform operations where each operation allows you to replace any element in the array with any integer value.
The goal is to make the array continuous. An array is considered continuous when it satisfies both conditions:
- All elements in the array are unique (no duplicates)
- The difference between the maximum and minimum elements equals
nums.length - 1
In other words, a continuous array of length n
contains exactly n
consecutive integers (though not necessarily in sorted order).
For example:
-
nums = [4, 2, 5, 3]
is continuous because:- All elements are unique
- Max (5) - Min (2) = 3, which equals array length (4) - 1
- This represents consecutive integers: 2, 3, 4, 5
-
nums = [1, 2, 3, 5, 6]
is not continuous because:- Max (6) - Min (1) = 5, but array length - 1 = 4
- The array is missing 4 to be truly consecutive
Your task is to find the minimum number of operations needed to transform the given array into a continuous array. Each operation replaces one element with any integer of your choice.
Intuition
The key insight is that we want to keep as many original elements as possible and only replace the minimum number of elements. Since the final continuous array must have exactly n
consecutive integers, we need to find which consecutive range would allow us to preserve the most original elements.
Think of it this way: if we decide the final continuous array should be [x, x+1, x+2, ..., x+n-1]
for some starting value x
, then we want to choose x
such that this range contains as many original elements from nums
as possible.
Here's the crucial observation: instead of trying all possible starting values x
, we can be smarter. The optimal range must align with at least one existing element in the array. Why? Because if we shift the range slightly to include one more existing element, we reduce the number of operations needed.
So our strategy becomes:
- Remove duplicates first (since the final array needs unique elements, duplicates must be replaced anyway)
- Sort the array to make range checking easier
- For each unique element, consider it as the potential minimum value of our final continuous array
- If element
nums[i]
is the minimum, then the maximum would benums[i] + n - 1
- Count how many existing unique elements fall within the range
[nums[i], nums[i] + n - 1]
- The elements outside this range need to be replaced
The number of operations needed equals n
minus the number of elements we can keep. By trying each unique element as the potential minimum and finding which gives us the maximum elements to keep, we minimize the operations needed.
Binary search helps us efficiently find how many elements fall within each range since the array is sorted.
Learn more about Binary Search and Sliding Window patterns.
Solution Approach
The implementation follows a systematic approach using sorting, deduplication, and binary search:
Step 1: Initialize and Deduplicate
ans = n = len(nums)
nums = sorted(set(nums))
- Store the original length
n
of the array - Remove duplicates using
set()
and sort the resulting unique elements - Initialize
ans
ton
(worst case: replace all elements)
Step 2: Enumerate Each Element as Potential Minimum
for i, v in enumerate(nums):
- Iterate through each unique element
v
at indexi
- Consider
v
as the minimum value of the potential continuous array
Step 3: Find the Range Upper Bound
j = bisect_right(nums, v + n - 1)
- If
v
is the minimum, the maximum would bev + n - 1
- Use
bisect_right
to find the first indexj
wherenums[j] > v + n - 1
- This gives us the range
[i, j)
containing all elements that fall within[v, v + n - 1]
Step 4: Calculate Operations Needed
ans = min(ans, n - (j - i))
j - i
represents the count of existing unique elements within the range[v, v + n - 1]
n - (j - i)
is the number of elements that need to be replaced- Update
ans
with the minimum operations found so far
Step 5: Return Result
return ans
Time Complexity: O(n log n)
due to sorting and binary search operations for each element
Space Complexity: O(n)
for storing the deduplicated sorted array
The algorithm efficiently finds the optimal consecutive range by trying each existing unique element as a potential minimum value and determining which choice requires the fewest replacements.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with nums = [1, 10, 100, 10, 1]
.
Step 1: Initialize and Deduplicate
- Original array:
[1, 10, 100, 10, 1]
withn = 5
- After removing duplicates and sorting:
nums = [1, 10, 100]
- Initialize
ans = 5
(worst case: replace all elements)
Step 2: Try Each Element as Potential Minimum
Iteration 1: Consider v = 1
as minimum
- Range would be
[1, 5]
(since1 + 5 - 1 = 5
) - Use binary search:
j = bisect_right([1, 10, 100], 5) = 1
- Elements in range
[1, 5]
: onlynums[0] = 1
- Count of elements to keep:
j - i = 1 - 0 = 1
- Operations needed:
5 - 1 = 4
- Update:
ans = min(5, 4) = 4
Iteration 2: Consider v = 10
as minimum
- Range would be
[10, 14]
(since10 + 5 - 1 = 14
) - Use binary search:
j = bisect_right([1, 10, 100], 14) = 2
- Elements in range
[10, 14]
: onlynums[1] = 10
- Count of elements to keep:
j - i = 2 - 1 = 1
- Operations needed:
5 - 1 = 4
- Update:
ans = min(4, 4) = 4
Iteration 3: Consider v = 100
as minimum
- Range would be
[100, 104]
(since100 + 5 - 1 = 104
) - Use binary search:
j = bisect_right([1, 10, 100], 104) = 3
- Elements in range
[100, 104]
: onlynums[2] = 100
- Count of elements to keep:
j - i = 3 - 2 = 1
- Operations needed:
5 - 1 = 4
- Update:
ans = min(4, 4) = 4
Result: Minimum operations needed = 4
We need to replace 4 elements. For example, if we keep 1
and replace the others with [2, 3, 4, 5]
, we get the continuous array [1, 2, 3, 4, 5]
.
Solution Implementation
1from typing import List
2from bisect import bisect_right
3
4class Solution:
5 def minOperations(self, nums: List[int]) -> int:
6 # Store the original length of the array
7 original_length = len(nums)
8
9 # Initialize minimum operations to worst case (changing all elements)
10 min_operations = original_length
11
12 # Remove duplicates and sort the array
13 # This gives us all unique values in ascending order
14 unique_sorted_nums = sorted(set(nums))
15
16 # Iterate through each unique value as a potential start of the continuous range
17 for start_index, start_value in enumerate(unique_sorted_nums):
18 # Calculate the maximum value that would be in a continuous range
19 # starting from start_value with original_length elements
20 # Range would be [start_value, start_value + original_length - 1]
21 max_value_in_range = start_value + original_length - 1
22
23 # Find the index right after the last element that fits in our range
24 # bisect_right returns the insertion point for max_value_in_range
25 end_index = bisect_right(unique_sorted_nums, max_value_in_range)
26
27 # Calculate how many unique values from the original array
28 # already fall within this continuous range
29 elements_in_range = end_index - start_index
30
31 # Calculate operations needed: total elements - elements already in range
32 operations_needed = original_length - elements_in_range
33
34 # Update minimum operations if we found a better solution
35 min_operations = min(min_operations, operations_needed)
36
37 return min_operations
38
1class Solution {
2 /**
3 * Finds the minimum number of operations to make the array continuous.
4 * A continuous array means the difference between max and min elements is exactly n-1,
5 * where n is the array length, and all elements are unique.
6 *
7 * @param nums the input array
8 * @return minimum number of operations needed
9 */
10 public int minOperations(int[] nums) {
11 int arrayLength = nums.length;
12
13 // Sort the array to process elements in order
14 Arrays.sort(nums);
15
16 // Remove duplicates by overwriting the array in-place
17 int uniqueCount = 1;
18 for (int i = 1; i < arrayLength; ++i) {
19 if (nums[i] != nums[i - 1]) {
20 nums[uniqueCount++] = nums[i];
21 }
22 }
23
24 // Initialize the answer with worst case (change all elements)
25 int minOperations = arrayLength;
26
27 // For each unique element as potential start of continuous array
28 for (int startIndex = 0; startIndex < uniqueCount; ++startIndex) {
29 // Find the rightmost element that can be included in the range
30 // The valid range is [nums[startIndex], nums[startIndex] + arrayLength - 1]
31 int maxValidValue = nums[startIndex] + arrayLength - 1;
32 int endIndex = search(nums, maxValidValue, startIndex, uniqueCount);
33
34 // Calculate elements that don't need to be changed
35 int elementsInRange = endIndex - startIndex;
36
37 // Update minimum operations needed
38 minOperations = Math.min(minOperations, arrayLength - elementsInRange);
39 }
40
41 return minOperations;
42 }
43
44 /**
45 * Binary search to find the leftmost position where nums[position] > target.
46 * Returns the number of elements <= target in the range [left, right).
47 *
48 * @param nums the sorted array to search
49 * @param target the target value to compare against
50 * @param left the left boundary (inclusive)
51 * @param right the right boundary (exclusive)
52 * @return the index of first element greater than target, or right if all elements <= target
53 */
54 private int search(int[] nums, int target, int left, int right) {
55 while (left < right) {
56 int mid = (left + right) >> 1; // Equivalent to (left + right) / 2
57
58 if (nums[mid] > target) {
59 // Mid element is too large, search in left half
60 right = mid;
61 } else {
62 // Mid element is valid, search in right half
63 left = mid + 1;
64 }
65 }
66
67 return left;
68 }
69}
70
1class Solution {
2public:
3 int minOperations(vector<int>& nums) {
4 // Sort the array in ascending order
5 sort(nums.begin(), nums.end());
6
7 // Remove duplicates and get the new size of unique elements
8 // unique() moves unique elements to the front and returns iterator to new end
9 int uniqueCount = unique(nums.begin(), nums.end()) - nums.begin();
10
11 // Store the original array size
12 int arraySize = nums.size();
13
14 // Initialize the minimum operations needed as the worst case (change all elements)
15 int minOps = arraySize;
16
17 // Try each unique element as the potential start of the continuous range
18 for (int startIdx = 0; startIdx < uniqueCount; ++startIdx) {
19 // Find the rightmost element that can be included in the range
20 // The range is [nums[startIdx], nums[startIdx] + arraySize - 1]
21 // upper_bound finds the first element greater than the target value
22 int endIdx = upper_bound(nums.begin() + startIdx,
23 nums.begin() + uniqueCount,
24 nums[startIdx] + arraySize - 1) - nums.begin();
25
26 // Calculate operations needed: total elements - elements already in range
27 // (endIdx - startIdx) represents the count of unique elements within the valid range
28 minOps = min(minOps, arraySize - (endIdx - startIdx));
29 }
30
31 return minOps;
32 }
33};
34
1/**
2 * Finds the minimum number of operations to make the array continuous.
3 * A continuous array means all elements are unique and max - min = n - 1.
4 * @param nums - The input array of numbers
5 * @returns The minimum number of operations needed
6 */
7function minOperations(nums: number[]): number {
8 const arrayLength: number = nums.length;
9
10 // Sort the array in ascending order
11 nums.sort((a: number, b: number) => a - b);
12
13 // Remove duplicates from the sorted array
14 let uniqueCount: number = 1;
15 for (let i: number = 1; i < arrayLength; ++i) {
16 if (nums[i] !== nums[i - 1]) {
17 nums[uniqueCount++] = nums[i];
18 }
19 }
20
21 // Initialize the answer with the worst case (replace all elements)
22 let minOps: number = arrayLength;
23
24 // For each unique element, consider it as the start of the continuous range
25 for (let i: number = 0; i < uniqueCount; ++i) {
26 // Find the rightmost element that can be in the range [nums[i], nums[i] + n - 1]
27 const targetValue: number = nums[i] + arrayLength - 1;
28 const rightBoundIndex: number = search(nums, targetValue, i, uniqueCount);
29
30 // Calculate operations needed: total elements - elements already in range
31 const elementsInRange: number = rightBoundIndex - i;
32 minOps = Math.min(minOps, arrayLength - elementsInRange);
33 }
34
35 return minOps;
36}
37
38/**
39 * Binary search to find the first index where nums[index] > target.
40 * Returns the index of the first element greater than x in the range [left, right).
41 * @param nums - The sorted array to search in
42 * @param x - The target value to compare against
43 * @param left - The left boundary of the search range (inclusive)
44 * @param right - The right boundary of the search range (exclusive)
45 * @returns The index of the first element greater than x
46 */
47function search(nums: number[], x: number, left: number, right: number): number {
48 while (left < right) {
49 // Calculate middle index using bit shift for efficiency
50 const mid: number = (left + right) >> 1;
51
52 if (nums[mid] > x) {
53 // If middle element is greater than x, search in left half
54 right = mid;
55 } else {
56 // If middle element is less than or equal to x, search in right half
57 left = mid + 1;
58 }
59 }
60
61 return left;
62}
63
Time and Space Complexity
The time complexity is O(n × log n)
, where n
is the length of the input array. This comes from:
- Converting to set:
O(n)
- Sorting the unique elements:
O(m × log m)
wherem ≤ n
is the number of unique elements - Iterating through unique elements:
O(m)
iterations - Each iteration performs
bisect_right
:O(log m)
per iteration - Overall:
O(n) + O(m × log m) + O(m × log m) = O(n × log n)
in the worst case when all elements are unique
The space complexity is O(n)
, not O(log n)
as stated in the reference. This comes from:
- Creating a set and sorted list of unique elements:
O(m)
wherem ≤ n
, which isO(n)
in the worst case - The sorting algorithm's recursive stack space:
O(log m)
which isO(log n)
in the worst case - Overall:
O(n)
dominates
Note: The reference answer states O(log n)
for space complexity, which would only account for the sorting stack space and assumes the sorted unique array doesn't count as extra space (perhaps considering it replaces the original array reference). However, since we create a new data structure from set(nums)
and then sort it, the more accurate space complexity is O(n)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Forgetting to Handle Duplicates
Pitfall: Working with the original array without removing duplicates leads to incorrect calculations. Duplicate values would be counted multiple times when determining elements within a range, causing the algorithm to underestimate the number of operations needed.
Example:
# Incorrect approach - not removing duplicates nums = [1, 1, 2, 2, 3] # If we don't deduplicate, we might incorrectly count 5 elements in range [1,5] # But we actually only have 3 unique values (1, 2, 3)
Solution: Always deduplicate the array before processing:
unique_sorted_nums = sorted(set(nums)) # Removes duplicates and sorts
2. Using Binary Search Incorrectly
Pitfall: Using bisect_left
instead of bisect_right
or calculating the wrong boundary value can lead to off-by-one errors.
Example:
# Incorrect - using bisect_left end_index = bisect_left(unique_sorted_nums, max_value_in_range) # This would miss elements equal to max_value_in_range # Incorrect - wrong boundary calculation max_value_in_range = start_value + original_length # Should be original_length - 1
Solution: Use bisect_right
to include all elements up to and including the maximum value:
max_value_in_range = start_value + original_length - 1 end_index = bisect_right(unique_sorted_nums, max_value_in_range)
3. Misunderstanding the Continuous Array Definition
Pitfall: Confusing "continuous" with "sorted" or thinking elements need to be consecutive in their current positions rather than just existing as consecutive values.
Example:
# Array [4, 2, 5, 3] is continuous even though not sorted # It contains consecutive integers: 2, 3, 4, 5 # Some might incorrectly think this needs operations to sort it
Solution: Remember that continuous means:
- All unique elements
- Max - Min = length - 1
- Position in array doesn't matter, only the values present
4. Not Considering All Possible Ranges
Pitfall: Only checking ranges starting from the minimum value in the array, missing potentially better solutions.
Example:
nums = [1, 10, 100, 200, 300] # If we only consider range starting at 1: [1, 2, 3, 4, 5] # We'd need to replace 4 elements # But range [100, 101, 102, 103, 104] only needs 2 replacements
Solution: Iterate through every unique value as a potential starting point:
for start_index, start_value in enumerate(unique_sorted_nums):
# Check range starting from each unique value
Which of the following is a good use case for backtracking?
Recommended Readings
Binary Search Speedrun For each of the Speedrun questions you will be given a binary search related problem and a corresponding multiple choice question The multiple choice questions are related to the techniques and template s introduced in the binary search section It's recommended that you have gone through at
https assets algo monster cover_photos stack svg Sliding Window Maximum Monotonic Stack We have an array and a sliding window defined by a start index and an end index The sliding window moves from left of the array to right There are always k elements in the window The window
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!