Facebook Pixel

2602. Minimum Operations to Make All Array Elements Equal

Problem Description

You have an array nums containing positive integers and an array queries of size m. For each query queries[i], you need to transform all elements in nums to equal queries[i].

You can perform this operation any number of times:

  • Increase or decrease any element in the array by 1

Your task is to find the minimum number of operations needed for each query. After processing each query, the array resets to its original state.

For example, if nums = [3, 1, 6, 8] and you want to make all elements equal to 5:

  • Change 3 to 5: needs 2 operations (increase by 2)
  • Change 1 to 5: needs 4 operations (increase by 4)
  • Change 6 to 5: needs 1 operation (decrease by 1)
  • Change 8 to 5: needs 3 operations (decrease by 3)
  • Total: 2 + 4 + 1 + 3 = 10 operations

The solution uses sorting and prefix sums to efficiently calculate the total operations. After sorting nums, for each query value x:

  • Elements less than x need to be increased to x
  • Elements greater than x need to be decreased to x
  • Binary search finds the boundary between these two groups
  • Prefix sums help calculate the total difference quickly

The formula for the minimum operations is:

  • For elements greater than x: sum of those elements - (count × x)
  • For elements less than x: (count × x) - sum of those elements

Return an array where answer[i] represents the minimum operations needed for queries[i].

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

Intuition

The key insight is that to transform all elements to a target value, we need to calculate the total "distance" all elements must travel. Each element's contribution to the total operations is simply |element - target|.

Initially, we might think to iterate through the array for each query and sum up these absolute differences. However, this would be inefficient with time complexity O(m × n).

The breakthrough comes from recognizing that we can split the problem into two parts:

  1. Elements smaller than the target need to move up
  2. Elements larger than the target need to move down

If we sort the array first, all elements less than the target will be on the left side, and all elements greater than the target will be on the right side. This natural partition allows us to use binary search to find the split point efficiently.

For the elements that need to increase (left side), the total operations would be: target × count_of_smaller_elements - sum_of_smaller_elements

For the elements that need to decrease (right side), the total operations would be: sum_of_larger_elements - target × count_of_larger_elements

To avoid recalculating sums repeatedly, we precompute a prefix sum array. This allows us to get the sum of any range in O(1) time.

The elegance of this approach is that sorting the array once enables us to:

  • Use binary search to find boundaries in O(log n) time
  • Use prefix sums to calculate range sums in O(1) time
  • Process each query in O(log n) time instead of O(n)

This reduces the overall complexity from O(m × n) to O(n log n + m log n), making it much more efficient for large inputs.

Learn more about Binary Search, Prefix Sum and Sorting patterns.

Solution Approach

Let's walk through the implementation step by step:

Step 1: Sort the array and build prefix sum

nums.sort()
s = list(accumulate(nums, initial=0))

We first sort nums in ascending order. Then we build a prefix sum array s where s[i] represents the sum of the first i elements. The initial=0 parameter ensures s[0] = 0, making the array length n+1.

Step 2: Process each query

For each query value x, we need to find two critical points using binary search:

i = bisect_left(nums, x + 1)

This finds the first index where nums[i] >= x + 1, meaning all elements from index i onwards are strictly greater than x. These elements need to be decreased to x.

The cost for decreasing these elements is:

t = s[-1] - s[i] - (len(nums) - i) * x
  • s[-1] - s[i] gives the sum of all elements from index i to the end
  • (len(nums) - i) * x is what their sum should be after changing them all to x
  • The difference is the total operations needed

Step 3: Handle elements less than x

i = bisect_left(nums, x)

This finds the first index where nums[i] >= x, meaning all elements before index i are strictly less than x. These elements need to be increased to x.

The cost for increasing these elements is:

t += x * i - s[i]
  • x * i is what the sum of the first i elements should be after changing them to x
  • s[i] is their current sum
  • The difference is the operations needed

Step 4: Combine results

The total operations for query x is the sum of both costs. We append this to our answer array and continue with the next query.

Time Complexity Analysis:

  • Sorting: O(n log n)
  • Building prefix sum: O(n)
  • For each query: O(log n) for binary searches
  • Total: O(n log n + m log n) where m is the number of queries

Space Complexity: O(n) for the prefix sum array

The beauty of this solution lies in preprocessing the data (sorting and prefix sums) to enable efficient query processing through binary search, avoiding the naive O(m × n) approach.

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 a concrete example with nums = [3, 1, 6, 8] and queries = [5, 2].

Initial Setup:

  1. Sort nums: [1, 3, 6, 8]
  2. Build prefix sum array: s = [0, 1, 4, 10, 18]
    • s[0] = 0
    • s[1] = 0 + 1 = 1
    • s[2] = 1 + 3 = 4
    • s[3] = 4 + 6 = 10
    • s[4] = 10 + 8 = 18

Query 1: x = 5

First, find elements greater than 5:

  • bisect_left(nums, 6) returns index 2
  • Elements at indices 2 and 3 (values 6 and 8) need to decrease to 5
  • Sum of these elements: s[4] - s[2] = 18 - 4 = 14
  • After transformation they should sum to: 2 × 5 = 10
  • Operations needed: 14 - 10 = 4

Next, find elements less than 5:

  • bisect_left(nums, 5) returns index 2
  • Elements at indices 0 and 1 (values 1 and 3) need to increase to 5
  • These elements should sum to: 2 × 5 = 10
  • Current sum: s[2] = 4
  • Operations needed: 10 - 4 = 6

Total operations: 4 + 6 = 10

Query 2: x = 2

Find elements greater than 2:

  • bisect_left(nums, 3) returns index 1
  • Elements at indices 1, 2, 3 (values 3, 6, 8) need to decrease to 2
  • Sum of these elements: s[4] - s[1] = 18 - 1 = 17
  • After transformation they should sum to: 3 × 2 = 6
  • Operations needed: 17 - 6 = 11

Find elements less than 2:

  • bisect_left(nums, 2) returns index 1
  • Element at index 0 (value 1) needs to increase to 2
  • This element should sum to: 1 × 2 = 2
  • Current sum: s[1] = 1
  • Operations needed: 2 - 1 = 1

Total operations: 11 + 1 = 12

Final Answer: [10, 12]

The algorithm efficiently calculates these totals using binary search to partition the sorted array and prefix sums to compute range sums in constant time.

Solution Implementation

1from typing import List
2from itertools import accumulate
3from bisect import bisect_left
4
5class Solution:
6    def minOperations(self, nums: List[int], queries: List[int]) -> List[int]:
7        # Sort the array to enable binary search and efficient calculation
8        nums.sort()
9      
10        # Create prefix sum array with initial 0 for easier indexing
11        # prefix_sums[i] = sum of nums[0] to nums[i-1]
12        prefix_sums = list(accumulate(nums, initial=0))
13      
14        result = []
15      
16        for target in queries:
17            # Find the first index where nums[index] > target
18            # All elements from this index need to be decreased to target
19            right_index = bisect_left(nums, target + 1)
20          
21            # Calculate operations needed to decrease all elements > target
22            # Sum of (nums[i] - target) for all i where nums[i] > target
23            decrease_operations = (prefix_sums[-1] - prefix_sums[right_index]) - (len(nums) - right_index) * target
24          
25            # Find the first index where nums[index] >= target
26            # All elements before this index need to be increased to target
27            left_index = bisect_left(nums, target)
28          
29            # Calculate operations needed to increase all elements < target
30            # Sum of (target - nums[i]) for all i where nums[i] < target
31            increase_operations = target * left_index - prefix_sums[left_index]
32          
33            # Total operations is sum of increases and decreases
34            total_operations = increase_operations + decrease_operations
35            result.append(total_operations)
36      
37        return result
38
1class Solution {
2    /**
3     * Calculates minimum operations to make all array elements equal to each query value.
4     * Operations involve incrementing or decrementing elements by 1.
5     * 
6     * @param nums The input array of numbers
7     * @param queries Array of target values to transform nums to
8     * @return List of minimum operations needed for each query
9     */
10    public List<Long> minOperations(int[] nums, int[] queries) {
11        // Sort the array to enable binary search and efficient calculation
12        Arrays.sort(nums);
13        int arrayLength = nums.length;
14      
15        // Build prefix sum array for quick range sum calculations
16        // prefixSum[i] represents sum of first i elements
17        long[] prefixSum = new long[arrayLength + 1];
18        for (int i = 0; i < arrayLength; i++) {
19            prefixSum[i + 1] = prefixSum[i] + nums[i];
20        }
21      
22        List<Long> result = new ArrayList<>();
23      
24        // Process each query
25        for (int targetValue : queries) {
26            // Find the first index where nums[index] > targetValue
27            int indexGreaterThan = binarySearch(nums, targetValue + 1);
28          
29            // Calculate operations for elements greater than targetValue
30            // These elements need to be decreased to targetValue
31            // Operations = sum of (nums[i] - targetValue) for all i where nums[i] > targetValue
32            long operationsForGreater = prefixSum[arrayLength] - prefixSum[indexGreaterThan] - 
33                                        (long)(arrayLength - indexGreaterThan) * targetValue;
34          
35            // Find the first index where nums[index] >= targetValue
36            int indexGreaterOrEqual = binarySearch(nums, targetValue);
37          
38            // Calculate operations for elements less than targetValue
39            // These elements need to be increased to targetValue
40            // Operations = sum of (targetValue - nums[i]) for all i where nums[i] < targetValue
41            long operationsForLess = (long)targetValue * indexGreaterOrEqual - prefixSum[indexGreaterOrEqual];
42          
43            // Total operations is sum of both
44            result.add(operationsForGreater + operationsForLess);
45        }
46      
47        return result;
48    }
49  
50    /**
51     * Binary search to find the leftmost position where nums[position] >= target
52     * 
53     * @param nums Sorted array to search in
54     * @param target Target value to search for
55     * @return Index of first element >= target, or array length if all elements < target
56     */
57    private int binarySearch(int[] nums, int target) {
58        int left = 0;
59        int right = nums.length;
60      
61        while (left < right) {
62            int mid = (left + right) >>> 1;  // Using unsigned shift to avoid overflow
63          
64            if (nums[mid] >= target) {
65                // Target might be at mid or to the left
66                right = mid;
67            } else {
68                // Target is definitely to the right
69                left = mid + 1;
70            }
71        }
72      
73        return left;
74    }
75}
76
1class Solution {
2public:
3    vector<long long> minOperations(vector<int>& nums, vector<int>& queries) {
4        // Sort the array to enable binary search and calculation optimization
5        sort(nums.begin(), nums.end());
6      
7        int n = nums.size();
8      
9        // Build prefix sum array for quick range sum calculation
10        // prefixSum[i] stores the sum of first i elements
11        vector<long long> prefixSum(n + 1);
12        for (int i = 0; i < n; ++i) {
13            prefixSum[i + 1] = prefixSum[i] + nums[i];
14        }
15      
16        vector<long long> result;
17      
18        // Process each query
19        for (auto& target : queries) {
20            // Find the first element greater than target
21            // This gives us the split point where elements need to decrease vs increase
22            int rightIndex = lower_bound(nums.begin(), nums.end(), target + 1) - nums.begin();
23          
24            // Calculate operations needed for elements greater than target
25            // These elements need to be decreased to target
26            // Operations = sum of elements from rightIndex to end - (count * target)
27            long long operationsForGreater = prefixSum[n] - prefixSum[rightIndex] - 
28                                            1LL * (n - rightIndex) * target;
29          
30            // Find the first element greater than or equal to target
31            int leftIndex = lower_bound(nums.begin(), nums.end(), target) - nums.begin();
32          
33            // Calculate operations needed for elements less than target
34            // These elements need to be increased to target
35            // Operations = (count * target) - sum of elements from 0 to leftIndex
36            long long operationsForLess = 1LL * target * leftIndex - prefixSum[leftIndex];
37          
38            // Total operations is the sum of both parts
39            result.push_back(operationsForGreater + operationsForLess);
40        }
41      
42        return result;
43    }
44};
45
1/**
2 * Calculates minimum operations to make all elements equal to each query value
3 * @param nums - Array of numbers to transform
4 * @param queries - Target values for transformation
5 * @returns Array of minimum operations for each query
6 */
7function minOperations(nums: number[], queries: number[]): number[] {
8    // Sort the input array in ascending order
9    nums.sort((a, b) => a - b);
10  
11    const arrayLength: number = nums.length;
12  
13    // Build prefix sum array for efficient range sum queries
14    // prefixSum[i] represents sum of first i elements
15    const prefixSum: number[] = new Array(arrayLength + 1).fill(0);
16    for (let i = 0; i < arrayLength; ++i) {
17        prefixSum[i + 1] = prefixSum[i] + nums[i];
18    }
19  
20    /**
21     * Binary search to find the leftmost position where nums[position] >= target
22     * @param target - The value to search for
23     * @returns Index of the first element >= target
24     */
25    const binarySearch = (target: number): number => {
26        let left: number = 0;
27        let right: number = arrayLength;
28      
29        while (left < right) {
30            const mid: number = (left + right) >> 1; // Equivalent to Math.floor((left + right) / 2)
31          
32            if (nums[mid] >= target) {
33                right = mid;
34            } else {
35                left = mid + 1;
36            }
37        }
38      
39        return left;
40    };
41  
42    const result: number[] = [];
43  
44    // Process each query
45    for (const queryValue of queries) {
46        // Find the index where elements are greater than queryValue
47        const greaterThanIndex: number = binarySearch(queryValue + 1);
48      
49        // Calculate operations for elements greater than queryValue
50        // These elements need to be decreased to queryValue
51        let totalOperations: number = prefixSum[arrayLength] - prefixSum[greaterThanIndex] - 
52                                      (arrayLength - greaterThanIndex) * queryValue;
53      
54        // Find the index where elements are greater than or equal to queryValue
55        const greaterOrEqualIndex: number = binarySearch(queryValue);
56      
57        // Calculate operations for elements less than queryValue
58        // These elements need to be increased to queryValue
59        totalOperations += queryValue * greaterOrEqualIndex - prefixSum[greaterOrEqualIndex];
60      
61        result.push(totalOperations);
62    }
63  
64    return result;
65}
66

Time and Space Complexity

Time Complexity: O((n + m) × log n) where n is the length of nums and m is the length of queries.

  • Sorting nums takes O(n log n) time
  • Building the prefix sum array using accumulate takes O(n) time
  • For each query in queries (m iterations):
    • Two bisect_left operations, each taking O(log n) time
    • Constant time arithmetic operations
    • Total per query: O(log n)
  • Overall: O(n log n + n + m × log n) = O((n + m) × log n)

Note: The reference answer shows O(n × log n), which appears to be considering only the case where m ≈ n or focusing primarily on the sorting complexity when n dominates.

Space Complexity: O(n)

  • The sorted nums array is modified in-place (no extra space)
  • The prefix sum array s requires O(n + 1) = O(n) space
  • The answer array ans requires O(m) space, but this is typically considered output space
  • Other variables (i, t, x) use O(1) space
  • Overall auxiliary space: O(n)

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

Common Pitfalls

1. Confusing Binary Search Boundaries

One of the most common mistakes is misunderstanding what bisect_left(nums, x) and bisect_left(nums, x + 1) actually return, leading to incorrect index calculations.

The Pitfall:

# INCORRECT: Using bisect_right for elements greater than x
right_index = bisect_right(nums, x)  # This gives wrong boundary!

Why it's wrong: bisect_right(nums, x) returns the insertion point AFTER all elements equal to x. If there are elements equal to x in the array, they don't need any operations, but this approach would incorrectly include them in the calculation for elements that need to be decreased.

The Solution:

# CORRECT: Use bisect_left with x + 1
right_index = bisect_left(nums, x + 1)

This correctly identifies the first element strictly greater than x.

2. Off-by-One Errors in Prefix Sum Indexing

The Pitfall:

# INCORRECT: Forgetting that prefix_sums has length n+1
prefix_sums = list(accumulate(nums))  # No initial=0
# Later trying to use prefix_sums[i] incorrectly

Why it's wrong: Without initial=0, the prefix sum array has length n instead of n+1, making index calculations confusing and error-prone.

The Solution:

# CORRECT: Always use initial=0 for cleaner indexing
prefix_sums = list(accumulate(nums, initial=0))
# Now prefix_sums[i] represents sum of first i elements

3. Forgetting to Sort the Array

The Pitfall:

def minOperations(self, nums: List[int], queries: List[int]) -> List[int]:
    # INCORRECT: Directly using unsorted array
    prefix_sums = list(accumulate(nums, initial=0))
    # Binary search won't work correctly!

Why it's wrong: Binary search requires a sorted array. Using it on an unsorted array gives meaningless results.

The Solution:

# CORRECT: Always sort first
nums.sort()
prefix_sums = list(accumulate(nums, initial=0))

4. Integer Overflow in Large Datasets

The Pitfall: While Python handles big integers automatically, in other languages or when optimizing:

# Potential issue in languages with fixed integer sizes
total = sum_of_elements - count * target  # Could overflow

The Solution: In Python this isn't an issue, but be aware when porting to other languages. Use appropriate data types (like long long in C++) or check for overflow conditions.

5. Misunderstanding the Problem Requirements

The Pitfall:

# INCORRECT: Modifying the original array
nums.sort()
# Process queries...
# Forgetting that each query should work on the ORIGINAL array state

Why it's wrong: The problem states the array resets after each query. However, sorting doesn't affect the solution since we're calculating absolute differences.

The Solution: The current approach is actually correct because:

  • We only need the total operations count
  • The order doesn't matter for calculating |nums[i] - target|
  • Sorting helps optimize the calculation but doesn't change the result

6. Inefficient Calculation Without Binary Search

The Pitfall:

# INCORRECT: Naive O(n) calculation for each query
for target in queries:
    operations = sum(abs(num - target) for num in nums)

Why it's wrong: While this gives the correct answer, it's O(m × n) time complexity, which is inefficient for large inputs.

The Solution: Use the binary search approach with prefix sums to achieve O(m log n) complexity after O(n log n) preprocessing.

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

Which data structure is used to implement priority queue?


Recommended Readings

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

Load More