Facebook Pixel

2170. Minimum Operations to Make the Array Alternating

MediumGreedyArrayHash TableCounting
Leetcode Link

Problem Description

You are given a 0-indexed array nums consisting of n positive integers.

The array nums is called alternating if it satisfies two conditions:

  1. Elements that are 2 positions apart must be equal: nums[i - 2] == nums[i] for all valid indices where 2 <= i <= n - 1
  2. Adjacent elements must be different: nums[i - 1] != nums[i] for all valid indices where 1 <= i <= n - 1

In simpler terms, an alternating array follows a pattern where:

  • All elements at even positions (indices 0, 2, 4, ...) have the same value
  • All elements at odd positions (indices 1, 3, 5, ...) have the same value
  • The value at even positions must be different from the value at odd positions

For example, [3, 1, 3, 1, 3] is alternating because all even positions contain 3, all odd positions contain 1, and 3 β‰  1.

In one operation, you can choose any index i and change nums[i] to any positive integer.

Your task is to find the minimum number of operations required to transform the given array into an alternating array.

For instance, if nums = [3, 1, 3, 2, 4, 3], you could make it alternating by changing index 3 from 2 to 1 and index 4 from 4 to 3, resulting in [3, 1, 3, 1, 3, 1] with just 2 operations.

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

Intuition

To make the array alternating with minimum operations, we need to think about what values should appear at even positions and odd positions. Since all even positions must have the same value and all odd positions must have the same value (but different from even positions), we want to maximize the number of elements that are already in the correct position.

The key insight is that we should choose the most frequent values already present at even and odd positions respectively. Why? Because if a value appears frequently at even positions, keeping it means fewer changes are needed at those positions.

Let's break down the strategy:

  1. Count the frequency of each value at even positions (indices 0, 2, 4, ...)
  2. Count the frequency of each value at odd positions (indices 1, 3, 5, ...)
  3. Find the most frequent value at even positions and the most frequent value at odd positions

However, there's a catch: what if the most frequent value at even positions is the same as the most frequent value at odd positions? We can't use the same value for both since adjacent elements must be different.

This leads us to track not just the most frequent value at each position type, but also the second most frequent. If the most frequent values are the same for even and odd positions, we have two choices:

  • Use the most frequent value at even positions and the second most frequent at odd positions
  • Use the second most frequent value at even positions and the most frequent at odd positions

We choose whichever option preserves more existing elements (requires fewer changes).

The total operations needed equals n (total elements) minus the number of elements we can keep unchanged. By maximizing the elements we keep, we minimize the operations required.

Learn more about Greedy patterns.

Solution Approach

The implementation uses a helper function f(i) that analyzes elements at positions starting from index i with a step of 2 (i.e., nums[i::2]). This allows us to separately analyze even positions (when i=0) and odd positions (when i=1).

Helper Function f(i):

  1. Uses Counter to count the frequency of each value at positions i, i+2, i+4, ...
  2. Tracks two variables k1 and k2 representing the most frequent and second most frequent values
  3. Iterates through the counter to find these top two values:
    • If a value's count is greater than the current most frequent (cnt[k1] < v), it becomes the new most frequent, and the previous most frequent becomes second
    • If a value's count is greater than the current second most frequent but not the first (cnt[k2] < v), it becomes the new second most frequent
  4. Returns a tuple: (k1, cnt[k1], k2, cnt[k2]) containing the most frequent value, its count, second most frequent value, and its count

Main Algorithm:

  1. Call f(0) to get the top two values and their counts at even positions, stored in tuple a

    • a[0]: most frequent value at even positions
    • a[1]: count of most frequent value at even positions
    • a[2]: second most frequent value at even positions
    • a[3]: count of second most frequent value at even positions
  2. Call f(1) to get the top two values and their counts at odd positions, stored in tuple b

    • Similar structure as tuple a but for odd positions
  3. Check if the most frequent values are different (a[0] != b[0]):

    • If different: We can use both most frequent values. The minimum operations = n - (a[1] + b[1])
    • This means we keep a[1] elements at even positions and b[1] elements at odd positions unchanged
  4. If the most frequent values are the same (a[0] == b[0]):

    • We have two options:
      • Keep the most frequent at even positions and second most frequent at odd positions: a[1] + b[3] elements unchanged
      • Keep the second most frequent at even positions and most frequent at odd positions: a[3] + b[1] elements unchanged
    • Choose the option that keeps more elements: n - max(a[1] + b[3], a[3] + b[1])

The time complexity is O(n) for counting elements, and space complexity is O(n) for storing the frequency counts.

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 = [2, 4, 2, 4, 2, 1].

Step 1: Analyze even positions (indices 0, 2, 4)

  • Elements at even positions: [2, 2, 2]
  • Using f(0):
    • Count frequencies: {2: 3}
    • Most frequent value k1 = 2 with count 3
    • Second most frequent k2 = 0 with count 0 (default, no other values)
    • Returns: (2, 3, 0, 0)

Step 2: Analyze odd positions (indices 1, 3, 5)

  • Elements at odd positions: [4, 4, 1]
  • Using f(1):
    • Count frequencies: {4: 2, 1: 1}
    • Most frequent value k1 = 4 with count 2
    • Second most frequent k2 = 1 with count 1
    • Returns: (4, 2, 1, 1)

Step 3: Determine minimum operations

  • Check if most frequent values are different: 2 β‰  4 βœ“
  • Since they're different, we can use both:
    • Keep value 2 at all even positions (3 elements unchanged)
    • Keep value 4 at odd positions (2 elements unchanged)
    • Total elements we can keep: 3 + 2 = 5
    • Minimum operations: 6 - 5 = 1

Result: We need only 1 operation - change index 5 from 1 to 4, giving us [2, 4, 2, 4, 2, 4].


Another example with conflict: nums = [3, 1, 3, 3, 3]

Step 1: Even positions (indices 0, 2, 4)

  • Elements: [3, 3, 3]
  • f(0) returns: (3, 3, 0, 0)

Step 2: Odd positions (indices 1, 3)

  • Elements: [1, 3]
  • f(1) returns: (1, 1, 3, 1) or (3, 1, 1, 1) depending on iteration order

Let's say f(1) returns (3, 1, 1, 1) (3 appears once, 1 appears once)

Step 3: Handle conflict

  • Most frequent values are the same: 3 == 3
  • Two options:
    • Use 3 at even, 1 at odd: Keep 3 + 1 = 4 elements
    • Use 0 at even, 3 at odd: Keep 0 + 1 = 1 element
  • Choose first option (keeps more): 5 - 4 = 1 operation

Result: Change index 3 from 3 to 1, giving us [3, 1, 3, 1, 3].

Solution Implementation

1class Solution:
2    def minimumOperations(self, nums: List[int]) -> int:
3        """
4        Find minimum operations to make array have alternating pattern where
5        all even indices have the same value and all odd indices have the same value.
6        Each operation changes one element to any value.
7        """
8      
9        def find_top_two_frequent(start_index: int) -> Tuple[int, int, int, int]:
10            """
11            Find the two most frequent values at positions start_index, start_index+2, start_index+4, ...
12          
13            Args:
14                start_index: Starting index (0 for even positions, 1 for odd positions)
15          
16            Returns:
17                Tuple of (most_frequent_value, most_frequent_count, 
18                         second_frequent_value, second_frequent_count)
19            """
20            # Initialize the two most frequent values
21            most_frequent_val = 0
22            second_frequent_val = 0
23          
24            # Count occurrences of each value at the specified positions
25            frequency_map = Counter(nums[start_index::2])
26          
27            # Find the two most frequent values
28            for value, count in frequency_map.items():
29                if frequency_map[most_frequent_val] < count:
30                    # Found a new most frequent value
31                    second_frequent_val = most_frequent_val
32                    most_frequent_val = value
33                elif frequency_map[second_frequent_val] < count:
34                    # Found a new second most frequent value
35                    second_frequent_val = value
36          
37            return (most_frequent_val, frequency_map[most_frequent_val], 
38                    second_frequent_val, frequency_map[second_frequent_val])
39      
40        # Get top two frequent values for even and odd positions
41        even_stats = find_top_two_frequent(0)  # Even indices (0, 2, 4, ...)
42        odd_stats = find_top_two_frequent(1)   # Odd indices (1, 3, 5, ...)
43      
44        total_length = len(nums)
45      
46        # If the most frequent values at even and odd positions are different
47        if even_stats[0] != odd_stats[0]:
48            # Keep the most frequent values at both positions
49            # Operations needed = total - (kept even values + kept odd values)
50            return total_length - (even_stats[1] + odd_stats[1])
51      
52        # If the most frequent values are the same, we need to use second choice for one position
53        # Option 1: Keep most frequent at even positions, second frequent at odd positions
54        # Option 2: Keep second frequent at even positions, most frequent at odd positions
55        return total_length - max(even_stats[1] + odd_stats[3], 
56                                   even_stats[3] + odd_stats[1])
57
1class Solution {
2    /**
3     * Finds the minimum number of operations to make the array alternating.
4     * An alternating array means nums[i] != nums[i+1] for all valid i.
5     * Each operation allows changing one element to any value.
6     * 
7     * @param nums the input array
8     * @return minimum number of operations needed
9     */
10    public int minimumOperations(int[] nums) {
11        // Get the two most frequent values at even indices (0, 2, 4, ...)
12        int[] evenPositionStats = findTopTwoFrequent(nums, 0);
13      
14        // Get the two most frequent values at odd indices (1, 3, 5, ...)
15        int[] oddPositionStats = findTopTwoFrequent(nums, 1);
16      
17        int arrayLength = nums.length;
18      
19        // If the most frequent values at even and odd positions are different
20        // We can keep both and change the rest
21        if (evenPositionStats[0] != oddPositionStats[0]) {
22            return arrayLength - (evenPositionStats[1] + oddPositionStats[1]);
23        }
24      
25        // If the most frequent values are the same, we need to use one of them
26        // with the second most frequent from the other position
27        // Option 1: Keep most frequent from even positions + second most frequent from odd positions
28        // Option 2: Keep second most frequent from even positions + most frequent from odd positions
29        return arrayLength - Math.max(
30            evenPositionStats[1] + oddPositionStats[3],  // Even's most frequent + Odd's second most frequent
31            evenPositionStats[3] + oddPositionStats[1]   // Even's second most frequent + Odd's most frequent
32        );
33    }
34
35    /**
36     * Finds the two most frequent values at positions starting from startIndex with step 2.
37     * 
38     * @param nums the input array
39     * @param startIndex starting index (0 for even positions, 1 for odd positions)
40     * @return array of [mostFrequentValue, mostFrequentCount, secondMostFrequentValue, secondMostFrequentCount]
41     */
42    private int[] findTopTwoFrequent(int[] nums, int startIndex) {
43        int mostFrequentValue = 0;
44        int secondMostFrequentValue = 0;
45      
46        // Count frequency of each value at the specified positions
47        Map<Integer, Integer> frequencyMap = new HashMap<>();
48        for (int i = startIndex; i < nums.length; i += 2) {
49            frequencyMap.merge(nums[i], 1, Integer::sum);
50        }
51      
52        // Find the two most frequent values
53        for (Map.Entry<Integer, Integer> entry : frequencyMap.entrySet()) {
54            int currentValue = entry.getKey();
55            int currentFrequency = entry.getValue();
56          
57            // Check if current frequency is higher than the most frequent
58            if (frequencyMap.getOrDefault(mostFrequentValue, 0) < currentFrequency) {
59                // Shift the previous most frequent to second place
60                secondMostFrequentValue = mostFrequentValue;
61                mostFrequentValue = currentValue;
62            } 
63            // Check if current frequency is higher than the second most frequent
64            else if (frequencyMap.getOrDefault(secondMostFrequentValue, 0) < currentFrequency) {
65                secondMostFrequentValue = currentValue;
66            }
67        }
68      
69        // Return the values and their frequencies
70        return new int[] {
71            mostFrequentValue, 
72            frequencyMap.getOrDefault(mostFrequentValue, 0),
73            secondMostFrequentValue, 
74            frequencyMap.getOrDefault(secondMostFrequentValue, 0)
75        };
76    }
77}
78
1class Solution {
2public:
3    int minimumOperations(vector<int>& nums) {
4        // Lambda function to find the two most frequent values at positions with given parity
5        // Returns: {most_frequent_value, its_count, second_most_frequent_value, its_count}
6        auto findTopTwoFrequent = [&](int startIndex) -> vector<int> {
7            int mostFreqValue = 0;
8            int secondMostFreqValue = 0;
9            unordered_map<int, int> frequencyMap;
10          
11            // Count frequency of values at positions startIndex, startIndex+2, startIndex+4, ...
12            for (int i = startIndex; i < nums.size(); i += 2) {
13                frequencyMap[nums[i]]++;
14            }
15          
16            // Find the two most frequent values
17            for (auto& [value, frequency] : frequencyMap) {
18                if (mostFreqValue == 0 || frequencyMap[mostFreqValue] < frequency) {
19                    // Found a new most frequent value
20                    secondMostFreqValue = mostFreqValue;
21                    mostFreqValue = value;
22                } else if (secondMostFreqValue == 0 || frequencyMap[secondMostFreqValue] < frequency) {
23                    // Found a new second most frequent value
24                    secondMostFreqValue = value;
25                }
26            }
27          
28            // Return the values and their frequencies
29            return {
30                mostFreqValue, 
31                mostFreqValue == 0 ? 0 : frequencyMap[mostFreqValue],
32                secondMostFreqValue, 
33                secondMostFreqValue == 0 ? 0 : frequencyMap[secondMostFreqValue]
34            };
35        };
36      
37        // Find most frequent values at even indices (0, 2, 4, ...)
38        vector<int> evenIndicesStats = findTopTwoFrequent(0);
39        // Find most frequent values at odd indices (1, 3, 5, ...)
40        vector<int> oddIndicesStats = findTopTwoFrequent(1);
41      
42        int totalElements = nums.size();
43      
44        // If the most frequent values at even and odd indices are different,
45        // we can keep them and change all other elements
46        if (evenIndicesStats[0] != oddIndicesStats[0]) {
47            return totalElements - (evenIndicesStats[1] + oddIndicesStats[1]);
48        }
49      
50        // If the most frequent values are the same, we need to use second most frequent
51        // for one of the positions to avoid having the same value at even and odd indices
52        // Choose the combination that keeps the maximum number of elements unchanged
53        return totalElements - max(
54            evenIndicesStats[1] + oddIndicesStats[3],  // Keep most freq at even, second most at odd
55            evenIndicesStats[3] + oddIndicesStats[1]   // Keep second most at even, most freq at odd
56        );
57    }
58};
59
1/**
2 * Finds minimum operations to make array alternating
3 * @param nums - Input array of numbers
4 * @returns Minimum number of operations needed
5 */
6function minimumOperations(nums: number[]): number {
7    /**
8     * Finds the two most frequent elements at alternating positions
9     * @param startIndex - Starting index (0 for even positions, 1 for odd positions)
10     * @returns Tuple of [most frequent element, its count, second most frequent element, its count]
11     */
12    const findTopTwoFrequent = (startIndex: number): [number, number, number, number] => {
13        // Count frequency of elements at alternating positions
14        const frequencyMap: Map<number, number> = new Map();
15        for (let index = startIndex; index < nums.length; index += 2) {
16            const currentValue = nums[index];
17            frequencyMap.set(currentValue, (frequencyMap.get(currentValue) || 0) + 1);
18        }
19
20        // Find the two most frequent elements
21        let mostFrequentKey: number = 0;
22        let secondMostFrequentKey: number = 0;
23      
24        for (const [key, frequency] of frequencyMap) {
25            if ((frequencyMap.get(mostFrequentKey) || 0) < frequency) {
26                // Current element is more frequent than the most frequent
27                secondMostFrequentKey = mostFrequentKey;
28                mostFrequentKey = key;
29            } else if ((frequencyMap.get(secondMostFrequentKey) || 0) < frequency) {
30                // Current element is more frequent than the second most frequent
31                secondMostFrequentKey = key;
32            }
33        }
34      
35        return [
36            mostFrequentKey, 
37            frequencyMap.get(mostFrequentKey) || 0,
38            secondMostFrequentKey, 
39            frequencyMap.get(secondMostFrequentKey) || 0
40        ];
41    };
42
43    // Get top two frequent elements at even positions (index 0, 2, 4, ...)
44    const evenPositionStats = findTopTwoFrequent(0);
45    // Get top two frequent elements at odd positions (index 1, 3, 5, ...)
46    const oddPositionStats = findTopTwoFrequent(1);
47  
48    const arrayLength = nums.length;
49  
50    // If the most frequent elements at even and odd positions are different
51    if (evenPositionStats[0] !== oddPositionStats[0]) {
52        // Keep the most frequent elements at both positions
53        return arrayLength - (evenPositionStats[1] + oddPositionStats[1]);
54    }
55  
56    // If the most frequent elements are the same, we need to use second most frequent
57    // Choose the combination that keeps the maximum number of elements
58    return arrayLength - Math.max(
59        evenPositionStats[1] + oddPositionStats[3],  // Keep most frequent at even, second most at odd
60        evenPositionStats[3] + oddPositionStats[1]   // Keep second most at even, most frequent at odd
61    );
62}
63

Time and Space Complexity

Time Complexity: O(n), where n is the length of the array nums.

The algorithm processes each element in the array exactly once when creating the Counter objects. The function f is called twice - once for even indices f(0) and once for odd indices f(1). Each call to f:

  • Creates a Counter from half of the array elements using nums[i::2], which takes O(n/2) time
  • Iterates through the Counter items once to find the two most frequent elements, which takes O(unique_elements) time, and in the worst case O(n/2) time

Since we call f twice and each call processes approximately n/2 elements, the total time is O(n/2) + O(n/2) = O(n).

Space Complexity: O(n), where n is the length of the array nums.

The space is primarily consumed by:

  • The Counter object in function f, which stores at most n/2 elements (either all even-indexed or all odd-indexed elements)
  • The function is called twice, but the Counter objects are not stored simultaneously - they are created, used, and then discarded
  • The variables a and b store tuples of fixed size (4 elements each), which is O(1)

In the worst case, when all elements at even (or odd) positions are unique, the Counter would store n/2 key-value pairs, resulting in O(n) space complexity.

Learn more about how to find time and space complexity quickly.

Common Pitfalls

Pitfall: Not Handling Edge Cases with Array Length ≀ 2

The Problem: The current implementation doesn't properly handle arrays with length 1 or 2. When nums has:

  • Length 1: The array is already alternating (trivially), requiring 0 operations
  • Length 2: If the two elements are different, it's already alternating (0 operations); if they're the same, we need 1 operation

The helper function find_top_two_frequent will attempt to access nums[start_index::2], which works but the Counter logic with uninitialized keys (frequency_map[0] when 0 is not in the map) will cause a KeyError.

Example of the Issue:

nums = [1]  # Length 1
# find_top_two_frequent(1) tries to access nums[1::2] which is empty
# frequency_map[most_frequent_val] with most_frequent_val=0 causes KeyError

nums = [1, 1]  # Length 2, same values
# The function might not handle this correctly

Solution: Add special handling for small arrays at the beginning of the main function:

class Solution:
    def minimumOperations(self, nums: List[int]) -> int:
        n = len(nums)
      
        # Handle edge cases
        if n == 1:
            return 0
        if n == 2:
            return 0 if nums[0] != nums[1] else 1
      
        def find_top_two_frequent(start_index: int) -> Tuple[int, int, int, int]:
            # Use defaultdict or handle empty slices
            frequency_map = Counter(nums[start_index::2])
          
            # Handle case where there are no elements at these positions
            if not frequency_map:
                return (0, 0, 0, 0)
          
            # Initialize with actual values from the map to avoid KeyError
            most_frequent_val = 0
            second_frequent_val = 0
            most_frequent_count = 0
            second_frequent_count = 0
          
            for value, count in frequency_map.items():
                if count > most_frequent_count:
                    second_frequent_val = most_frequent_val
                    second_frequent_count = most_frequent_count
                    most_frequent_val = value
                    most_frequent_count = count
                elif count > second_frequent_count:
                    second_frequent_val = value
                    second_frequent_count = count
          
            return (most_frequent_val, most_frequent_count, 
                    second_frequent_val, second_frequent_count)
      
        # Rest of the implementation remains the same...

Alternative Pitfall: Integer Overflow in Other Languages

While not an issue in Python, if implementing this in languages like C++ or Java, counting operations with large arrays could potentially cause integer overflow. Always use appropriate data types (like long in Java or long long in C++) when dealing with counts and sums.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of these pictures shows the visit order of a depth-first search?


Recommended Readings

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

Load More