Facebook Pixel

1551. Minimum Operations to Make Array Equal

Problem Description

You are given an array arr of length n where each element follows the pattern arr[i] = (2 * i) + 1 for all valid indices i (where 0 <= i < n).

This means the array looks like: [1, 3, 5, 7, 9, ...] - an array of consecutive odd numbers starting from 1.

You can perform operations on this array. In each operation, you:

  • Select two indices x and y (where 0 <= x, y < n)
  • Subtract 1 from arr[x]
  • Add 1 to arr[y]

Your goal is to make all elements in the array equal using the minimum number of operations.

For example, if n = 3, the initial array is [1, 3, 5]. To make all elements equal to 3:

  • Operation 1: Subtract 1 from index 2 and add 1 to index 0, resulting in [2, 3, 4]
  • Operation 2: Subtract 1 from index 2 and add 1 to index 0, resulting in [3, 3, 3]

The problem guarantees that it's always possible to make all elements equal. You need to return the minimum number of operations required to achieve this.

The solution leverages the fact that the sum of the array remains constant after each operation (since you're just moving values around). The target value for each element when they're all equal is the average of the array, which equals n. The minimum operations needed can be calculated by finding how much each element below the target needs to increase, which equals the sum of (n - arr[i]) for all elements where arr[i] < n.

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

Intuition

Let's think about what happens when we perform operations. Since each operation decreases one element by 1 and increases another by 1, the total sum of all elements remains constant throughout all operations.

The initial array is [1, 3, 5, 7, ...] with values (2*i + 1) for each index i. The sum of this arithmetic sequence is . Since we want all n elements to be equal and the sum stays constant, each element must equal n²/n = n in the final state.

Now, observe the initial array more carefully. The first half of elements are less than n (they need to increase), while the second half are greater than n (they need to decrease). For example, if n = 5, we have [1, 3, 5, 7, 9] and want all elements to be 5.

The key insight is that we can pair up elements symmetrically:

  • Element at index 0 (value 1) needs to increase by n - 1
  • Element at index n-1 (value 2n - 1) needs to decrease by n - 1

This pairing works perfectly! Each element in the first half can be paired with its corresponding element in the second half. The amount the smaller element needs to increase equals the amount the larger element needs to decrease.

Therefore, we only need to calculate the total amount that elements in the first half need to increase. For each index i in the first half (where i < n/2), the element value is 2*i + 1, and it needs to increase by n - (2*i + 1).

The minimum number of operations equals the sum of all these increases: Σ(n - (2*i + 1)) for i from 0 to n/2 - 1.

Learn more about Math patterns.

Solution Approach

Based on our intuition, we need to calculate the sum of increases needed for the first half of the array. Let's implement this mathematically.

The array elements are arr[i] = 2*i + 1 for i from 0 to n-1. We established that:

  • The target value for each element is n
  • We only need to consider the first n/2 elements (those less than n)
  • For each element at index i in the first half, we need n - (2*i + 1) operations

The total number of operations is:

Sum = Σ(n - (2*i + 1)) for i = 0 to (n/2 - 1)

Let's break down the implementation:

  1. Determine the range: We iterate through the first half of indices, which is range(n >> 1) or range(n // 2).

  2. Calculate operations for each element: For each index i in this range:

    • The current value is 2*i + 1 (which can be written as (i << 1) | 1 using bit operations)
    • The target value is n
    • Operations needed: n - (2*i + 1)
  3. Sum all operations: We sum up all these differences to get the total minimum operations.

The solution code uses bit operations for efficiency:

  • n >> 1 is equivalent to n // 2 (right shift by 1 bit)
  • i << 1 | 1 is equivalent to 2*i + 1 (left shift by 1 bit, then OR with 1)

The one-liner solution elegantly combines all these steps:

return sum(n - (i << 1 | 1) for i in range(n >> 1))

This calculates exactly what we derived: the sum of differences between the target value n and each element value 2*i + 1 for the first half of the array.

Ready to land your dream job?

Unlock your dream job with a 3-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a concrete example with n = 5.

Initial Setup:

  • Array: [1, 3, 5, 7, 9] (using formula arr[i] = 2*i + 1)
  • Sum of array: 1 + 3 + 5 + 7 + 9 = 25
  • Target value for each element: 25 / 5 = 5
  • Goal: Make all elements equal to 5

Analysis:

  • Elements less than 5: [1, 3] at indices [0, 1]
  • Elements equal to 5: [5] at index [2]
  • Elements greater than 5: [7, 9] at indices [3, 4]

Calculating Required Operations:

For the first half (indices 0 to 1, since n//2 = 2):

  • Index 0: value = 1, needs to increase by 5 - 1 = 4
  • Index 1: value = 3, needs to increase by 5 - 3 = 2

Total operations = 4 + 2 = 6

Verification through actual operations:

  1. Move 1 from index 4 to index 0: [2, 3, 5, 7, 8]
  2. Move 1 from index 4 to index 0: [3, 3, 5, 7, 7]
  3. Move 1 from index 4 to index 0: [4, 3, 5, 7, 6]
  4. Move 1 from index 4 to index 0: [5, 3, 5, 7, 5]
  5. Move 1 from index 3 to index 1: [5, 4, 5, 6, 5]
  6. Move 1 from index 3 to index 1: [5, 5, 5, 5, 5]

Result: All elements are now 5, achieved in 6 operations.

Using the formula:

sum(n - (2*i + 1) for i in range(n // 2))
= sum(5 - (2*i + 1) for i in range(2))
= (5 - 1) + (5 - 3)
= 4 + 2
= 6

The formula correctly calculates that we need 6 operations, matching our manual verification.

Solution Implementation

1class Solution:
2    def minOperations(self, n: int) -> int:
3        """
4        Calculate minimum operations to make all elements equal in an array.
5      
6        The array contains n elements: [1, 3, 5, ..., 2n-1].
7        Each operation allows incrementing one element and decrementing another.
8        The target value is n (the average of all elements).
9      
10        Args:
11            n: The number of elements in the array
12          
13        Returns:
14            The minimum number of operations needed
15        """
16        # For the first half of elements (those less than n),
17        # calculate how many operations needed to bring them up to n
18        half_n = n // 2
19        total_operations = 0
20      
21        for i in range(half_n):
22            # Calculate the i-th element value: 2*i + 1
23            element_value = 2 * i + 1
24            # Add the difference between target (n) and current element
25            total_operations += n - element_value
26          
27        return total_operations
28
1class Solution {
2    public int minOperations(int n) {
3        // Initialize the total number of operations
4        int totalOperations = 0;
5      
6        // Iterate through the first half of the array
7        // n >> 1 is equivalent to n / 2
8        for (int i = 0; i < n / 2; i++) {
9            // Calculate the i-th odd number: 2 * i + 1
10            // (i << 1 | 1) is equivalent to (2 * i + 1)
11            int currentOddNumber = 2 * i + 1;
12          
13            // Add the difference between target value n and current odd number
14            // This represents the operations needed to make this element equal to n
15            totalOperations += n - currentOddNumber;
16        }
17      
18        // Return the total minimum operations required
19        return totalOperations;
20    }
21}
22
1class Solution {
2public:
3    int minOperations(int n) {
4        // Initialize the result counter for minimum operations
5        int result = 0;
6      
7        // Iterate through the first half of the array
8        // The array contains odd numbers: 1, 3, 5, 7, ..., (2n-1)
9        // We only need to process the first half since we're making all elements equal
10        for (int i = 0; i < n / 2; ++i) {
11            // Calculate the number of operations needed for element at index i
12            // Element at index i has value (2 * i + 1)
13            // Target value is n (the middle value when all elements are equal)
14            // Operations needed: n - (2 * i + 1)
15            result += n - (2 * i + 1);
16        }
17      
18        return result;
19    }
20};
21
1/**
2 * Calculates the minimum number of operations to make all elements in an array equal.
3 * The array is constructed as arr[i] = (2 * i) + 1 for i in range [0, n-1].
4 * Each operation increments one element and decrements another element by 1.
5 * 
6 * @param n - The size of the array
7 * @returns The minimum number of operations needed
8 */
9function minOperations(n: number): number {
10    let totalOperations: number = 0;
11  
12    // Iterate through the first half of the array
13    // We only need to check half since we're making pairs equal to the median
14    for (let i: number = 0; i < Math.floor(n / 2); i++) {
15        // Calculate the current element value: (2 * i) + 1
16        const currentElement: number = (i * 2) + 1;
17      
18        // Calculate operations needed to make this element equal to n (the target value)
19        // n - currentElement gives us the distance from the median
20        totalOperations += n - currentElement;
21    }
22  
23    return totalOperations;
24}
25

Time and Space Complexity

The time complexity is O(n), where n is the input parameter. The code uses a generator expression with range(n >> 1) which iterates n/2 times. Since we perform a constant amount of work (arithmetic operations: bit shift, bitwise OR, and subtraction) for each iteration, the overall time complexity is O(n/2) which simplifies to O(n).

The space complexity is O(1). The code uses a generator expression instead of creating a list, so it doesn't store all intermediate values in memory. The sum() function processes the generator values one at a time, maintaining only a running total. Therefore, only a constant amount of extra space is used regardless of the input size.

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

Common Pitfalls

1. Misunderstanding the Target Value

A common mistake is assuming the target value should be calculated by finding the sum and dividing by n, rather than recognizing that the target is always exactly n.

Pitfall Example:

def minOperations(self, n: int) -> int:
    # Wrong: Calculating average unnecessarily
    arr = [2*i + 1 for i in range(n)]
    target = sum(arr) // n  # This gives n, but wastes time
    # ... rest of solution

Solution: Recognize that for an arithmetic sequence of consecutive odd numbers starting from 1, the average (and median) is always n. This is a mathematical property that can be used directly.

2. Processing the Entire Array Instead of Half

Since operations move values from higher elements to lower elements, you only need to calculate operations for elements below the target. Processing all elements leads to incorrect results.

Pitfall Example:

def minOperations(self, n: int) -> int:
    total = 0
    for i in range(n):  # Wrong: iterating through all elements
        element = 2 * i + 1
        total += abs(n - element)  # This double-counts operations
    return total  # Returns twice the correct answer

Solution: Only iterate through the first n//2 elements, as these are the ones that need to be increased:

def minOperations(self, n: int) -> int:
    total = 0
    for i in range(n // 2):  # Correct: only first half
        element = 2 * i + 1
        total += n - element
    return total

3. Integer Division Edge Cases

When using bit operations or integer division, be careful with how Python handles these operations, especially for odd values of n.

Pitfall Example:

def minOperations(self, n: int) -> int:
    # Using bit shift without understanding it's floor division
    return sum(n - (2*i + 1) for i in range(n >> 1))
    # For n=5, this correctly gives range(2), but some might expect range(3)

Solution: Understand that n >> 1 and n // 2 both perform floor division. For odd n, the middle element (which equals n) doesn't need any operations, so using floor division is correct.

4. Overcomplicating with Actual Array Creation

Creating the actual array wastes memory and time when you can calculate values on-the-fly.

Pitfall Example:

def minOperations(self, n: int) -> int:
    # Unnecessary array creation
    arr = [2*i + 1 for i in range(n)]
    target = n
    operations = 0
    for val in arr[:n//2]:
        operations += target - val
    return operations

Solution: Calculate element values directly without storing them:

def minOperations(self, n: int) -> int:
    return sum(n - (2*i + 1) for i in range(n // 2))

5. Off-by-One Errors in Mathematical Formula

When deriving the closed-form solution, it's easy to make indexing mistakes.

Pitfall Example:

def minOperations(self, n: int) -> int:
    # Wrong formula due to incorrect index bounds
    half = n // 2
    return half * (half - 1)  # Incorrect formula

Solution: The correct closed-form formula is:

def minOperations(self, n: int) -> int:
    half = n // 2
    return half * (half + (n % 2))
    # Or simply: return n * n // 4
Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

Is the following code DFS or BFS?

void search(Node root) {
  if (!root) return;
  visit(root);
  root.visited = true;
  for (Node node in root.adjacent) {
    if (!node.visited) {
      search(node);
    }
  }
}

Recommended Readings

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

Load More