Facebook Pixel

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:

  1. All elements in the array are unique (no duplicates)
  2. 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.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

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:

  1. Remove duplicates first (since the final array needs unique elements, duplicates must be replaced anyway)
  2. Sort the array to make range checking easier
  3. For each unique element, consider it as the potential minimum value of our final continuous array
  4. If element nums[i] is the minimum, then the maximum would be nums[i] + n - 1
  5. Count how many existing unique elements fall within the range [nums[i], nums[i] + n - 1]
  6. 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 to n (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 index i
  • 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 be v + n - 1
  • Use bisect_right to find the first index j where nums[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 Evaluator

Example 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] with n = 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] (since 1 + 5 - 1 = 5)
  • Use binary search: j = bisect_right([1, 10, 100], 5) = 1
  • Elements in range [1, 5]: only nums[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] (since 10 + 5 - 1 = 14)
  • Use binary search: j = bisect_right([1, 10, 100], 14) = 2
  • Elements in range [10, 14]: only nums[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] (since 100 + 5 - 1 = 104)
  • Use binary search: j = bisect_right([1, 10, 100], 104) = 3
  • Elements in range [100, 104]: only nums[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) where m ≤ 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) where m ≤ n, which is O(n) in the worst case
  • The sorting algorithm's recursive stack space: O(log m) which is O(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
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of the following is a good use case for backtracking?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More