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 tox
- Elements greater than
x
need to be decreased tox
- 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]
.
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:
- Elements smaller than the target need to move up
- 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 ofO(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 indexi
to the end(len(nums) - i) * x
is what their sum should be after changing them all tox
- 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 firsti
elements should be after changing them tox
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)
wherem
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 EvaluatorExample Walkthrough
Let's walk through a concrete example with nums = [3, 1, 6, 8]
and queries = [5, 2]
.
Initial Setup:
- Sort nums:
[1, 3, 6, 8]
- 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
takesO(n log n)
time - Building the prefix sum array using
accumulate
takesO(n)
time - For each query in
queries
(m iterations):- Two
bisect_left
operations, each takingO(log n)
time - Constant time arithmetic operations
- Total per query:
O(log n)
- Two
- 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
requiresO(n + 1)
=O(n)
space - The answer array
ans
requiresO(m)
space, but this is typically considered output space - Other variables (
i
,t
,x
) useO(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.
Which data structure is used to implement priority queue?
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
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
Want a Structured Path to Master System Design Too? Don’t Miss This!