1775. Equal Sum Arrays With Minimum Number of Operations
Problem Description
You have two arrays nums1
and nums2
containing integers between 1 and 6 (inclusive). The arrays can have different lengths.
In each operation, you can pick any number from either array and change it to any value between 1 and 6.
Your goal is to make the sum of all numbers in nums1
equal to the sum of all numbers in nums2
using the minimum number of operations.
Return the minimum number of operations needed to achieve equal sums. If it's impossible to make the sums equal, return -1
.
Example scenarios:
- If
nums1 = [1,2,3,4,5,6]
andnums2 = [1,1,2,2,2,2]
, you need to find how many numbers to change to makesum(nums1) = 21
equal tosum(nums2) = 10
. - To maximize the impact of each operation, you'd want to decrease large numbers in the array with the larger sum (change 6β1, 5β1, etc.) or increase small numbers in the array with the smaller sum (change 1β6, 2β6, etc.).
Key insight: Each operation can change the total sum difference by at most 5 (changing a 1 to 6 or a 6 to 1). The solution calculates the maximum possible change for each number in both arrays, sorts these potential changes in descending order, and greedily applies them until the sum difference is eliminated.
Intuition
When we need to make two sums equal, we're essentially trying to eliminate the difference between them. Let's say sum(nums1) < sum(nums2)
, so we have a gap of d = sum(nums2) - sum(nums1)
to close.
To close this gap efficiently, we want each operation to have maximum impact. For the smaller sum array (nums1
), we want to increase values as much as possible - changing a 1 to 6 gives us +5, changing a 2 to 6 gives us +4, and so on. For the larger sum array (nums2
), we want to decrease values as much as possible - changing a 6 to 1 gives us -5 (which helps close the gap by 5), changing a 5 to 1 gives us -4, etc.
The key realization is that we can treat both types of changes uniformly:
- Increasing a value
v
innums1
to 6 contributes6 - v
toward closing the gap - Decreasing a value
v
innums2
to 1 contributesv - 1
toward closing the gap
By collecting all these potential contributions into a single array and sorting them in descending order, we can greedily pick the most impactful changes first. We keep applying changes until the cumulative contribution meets or exceeds the gap d
.
The greedy approach works because each change is independent - we're always better off making the change with the largest impact first. If the gap can be closed at all, this greedy strategy will find the minimum number of operations needed.
The impossibility check (-1
case) happens implicitly: if we've exhausted all possible changes and still haven't closed the gap, it means the gap is too large to bridge even with all values set to their extremes.
Learn more about Greedy patterns.
Solution Approach
The implementation follows the greedy strategy we identified:
-
Calculate the initial difference: First, compute
s1 = sum(nums1)
ands2 = sum(nums2)
. If they're already equal, return 0. -
Normalize the problem: To simplify the logic, we ensure
s1 < s2
by swapping the arrays if necessary. This way, we always work with the same scenario - trying to increase the smaller sum or decrease the larger sum. -
Build the contribution array: Create an array
arr
that contains all possible contributions:- For each value
v
innums1
(the smaller sum array), add6 - v
to represent how much we can increase the sum by changingv
to 6 - For each value
v
innums2
(the larger sum array), addv - 1
to represent how much we can decrease the sum by changingv
to 1
- For each value
-
Greedy selection: Sort
arr
in descending order to prioritize the most impactful changes. The difference to close isd = s2 - s1
. -
Apply changes until gap is closed: Iterate through the sorted contributions, subtracting each from
d
. Keep a counteri
for the number of operations. Onced <= 0
, we've closed the gap and returni
. -
Handle impossible cases: If we exhaust all possible changes (finish the loop) without closing the gap, return -1.
The time complexity is O((m + n) log(m + n))
where m
and n
are the lengths of the arrays, dominated by the sorting step. The space complexity is O(m + n)
for storing the contribution array.
The elegance of this solution lies in treating increases and decreases uniformly as "contributions" toward closing the gap, allowing us to use a simple greedy approach rather than complex decision-making about which array to modify.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example to illustrate the solution approach.
Example: nums1 = [1, 6, 6]
and nums2 = [1, 1, 1]
Step 1: Calculate initial sums
s1 = 1 + 6 + 6 = 13
s2 = 1 + 1 + 1 = 3
- Since
s1 > s2
, we swap the arrays to ensure the first array has the smaller sum - After swapping:
nums1 = [1, 1, 1]
(sum = 3) andnums2 = [1, 6, 6]
(sum = 13) - Gap to close:
d = 13 - 3 = 10
Step 2: Build the contribution array For each element, calculate how much it can contribute to closing the gap:
- From
nums1 = [1, 1, 1]
(we want to increase these):6 - 1 = 5
(changing 1β6 adds 5 to the sum)6 - 1 = 5
(changing 1β6 adds 5 to the sum)6 - 1 = 5
(changing 1β6 adds 5 to the sum)
- From
nums2 = [1, 6, 6]
(we want to decrease these):1 - 1 = 0
(changing 1β1 contributes nothing)6 - 1 = 5
(changing 6β1 subtracts 5 from the sum)6 - 1 = 5
(changing 6β1 subtracts 5 from the sum)
- Combined contributions:
arr = [5, 5, 5, 0, 5, 5]
Step 3: Sort contributions in descending order
- Sorted
arr = [5, 5, 5, 5, 5, 0]
Step 4: Greedily apply changes
- Start with gap
d = 10
- Operation 1: Use contribution of 5,
d = 10 - 5 = 5
- Operation 2: Use contribution of 5,
d = 5 - 5 = 0
- Gap closed! We need 2 operations
Verification: We could either:
- Change two 1s in the original
nums1
to 6s:[1, 6, 6] β [6, 6, 6]
, sum becomes 18 - Change both 6s in the original
nums2
to 1s:[1, 1, 1] β [1, 1, 1]
, sum stays 3 (Wait, that doesn't work since we need sum = 8 for both)
Actually, let me recalculate - if we change one 1β6 in the smaller array and one 6β1 in the larger array:
- Original
nums1 = [1, 1, 1]
becomes[6, 1, 1]
, sum = 8 - Original
nums2 = [1, 6, 6]
becomes[1, 1, 6]
, sum = 8 - Both sums equal 8, achieved in 2 operations β
Solution Implementation
1class Solution:
2 def minOperations(self, nums1: List[int], nums2: List[int]) -> int:
3 # Calculate the sum of both arrays
4 sum1, sum2 = sum(nums1), sum(nums2)
5
6 # If sums are already equal, no operations needed
7 if sum1 == sum2:
8 return 0
9
10 # Ensure sum1 is always the smaller sum for consistency
11 # If sum1 > sum2, swap the arrays and recur
12 if sum1 > sum2:
13 return self.minOperations(nums2, nums1)
14
15 # Calculate maximum possible increase for each element in nums1 (can change to 6)
16 # and maximum possible decrease for each element in nums2 (can change to 1)
17 max_changes = [6 - value for value in nums1] + [value - 1 for value in nums2]
18
19 # Calculate the difference we need to make up
20 difference = sum2 - sum1
21
22 # Sort changes in descending order to greedily use largest changes first
23 # Enumerate starting from 1 to count operations
24 for operation_count, change_value in enumerate(sorted(max_changes, reverse=True), 1):
25 # Apply the change and reduce the difference
26 difference -= change_value
27
28 # If we've made up the difference, return the operation count
29 if difference <= 0:
30 return operation_count
31
32 # If we can't make the sums equal, return -1
33 return -1
34
1class Solution {
2 public int minOperations(int[] nums1, int[] nums2) {
3 // Calculate the sum of both arrays
4 int sum1 = Arrays.stream(nums1).sum();
5 int sum2 = Arrays.stream(nums2).sum();
6
7 // If sums are already equal, no operations needed
8 if (sum1 == sum2) {
9 return 0;
10 }
11
12 // Ensure sum1 is always smaller than sum2 for consistency
13 // If not, swap the arrays and process recursively
14 if (sum1 > sum2) {
15 return minOperations(nums2, nums1);
16 }
17
18 // Calculate the difference we need to bridge
19 int difference = sum2 - sum1;
20
21 // Create an array to store all possible changes
22 // Each element represents how much we can change in one operation
23 int[] possibleChanges = new int[nums1.length + nums2.length];
24 int index = 0;
25
26 // For nums1: we can increase each element to 6 (maximum value)
27 // So the change is (6 - currentValue)
28 for (int value : nums1) {
29 possibleChanges[index++] = 6 - value;
30 }
31
32 // For nums2: we can decrease each element to 1 (minimum value)
33 // So the change is (currentValue - 1)
34 for (int value : nums2) {
35 possibleChanges[index++] = value - 1;
36 }
37
38 // Sort the changes in ascending order
39 Arrays.sort(possibleChanges);
40
41 // Greedily use the largest changes first to minimize operations
42 int operationCount = 1;
43 for (int i = possibleChanges.length - 1; i >= 0; i--) {
44 difference -= possibleChanges[i];
45
46 // If we've bridged the gap, return the number of operations
47 if (difference <= 0) {
48 return operationCount;
49 }
50
51 operationCount++;
52 }
53
54 // If we can't make the sums equal, return -1
55 return -1;
56 }
57}
58
1class Solution {
2public:
3 int minOperations(vector<int>& nums1, vector<int>& nums2) {
4 // Calculate the sum of both arrays
5 int sum1 = accumulate(nums1.begin(), nums1.end(), 0);
6 int sum2 = accumulate(nums2.begin(), nums2.end(), 0);
7
8 // If sums are already equal, no operations needed
9 if (sum1 == sum2) {
10 return 0;
11 }
12
13 // Ensure sum1 is always the smaller sum for consistent processing
14 // If sum1 > sum2, swap the arrays and recursively call
15 if (sum1 > sum2) {
16 return minOperations(nums2, nums1);
17 }
18
19 // Calculate the difference we need to eliminate
20 int difference = sum2 - sum1;
21
22 // Create an array to store the maximum change possible for each element
23 // For nums1: we can increase each element to 6 (maximum change: 6 - element)
24 // For nums2: we can decrease each element to 1 (maximum change: element - 1)
25 int totalSize = nums1.size() + nums2.size();
26 int maxChanges[totalSize];
27 int index = 0;
28
29 // Add maximum possible increases from nums1 (changing each to 6)
30 for (int& value : nums1) {
31 maxChanges[index++] = 6 - value;
32 }
33
34 // Add maximum possible decreases from nums2 (changing each to 1)
35 for (int& value : nums2) {
36 maxChanges[index++] = value - 1;
37 }
38
39 // Sort the changes in descending order to use the largest changes first
40 sort(maxChanges, maxChanges + index, greater<>());
41
42 // Greedily apply the largest changes until the difference is eliminated
43 for (int i = 0; i < index; ++i) {
44 difference -= maxChanges[i];
45 if (difference <= 0) {
46 return i + 1; // Return the number of operations performed
47 }
48 }
49
50 // If we cannot eliminate the difference, return -1
51 return -1;
52 }
53};
54
1function minOperations(nums1: number[], nums2: number[]): number {
2 // Calculate the sum of both arrays
3 let sum1 = nums1.reduce((acc, val) => acc + val, 0);
4 let sum2 = nums2.reduce((acc, val) => acc + val, 0);
5
6 // If sums are already equal, no operations needed
7 if (sum1 === sum2) {
8 return 0;
9 }
10
11 // Ensure sum1 is always the smaller sum for consistent processing
12 // If sum1 > sum2, swap the arrays and recursively call
13 if (sum1 > sum2) {
14 return minOperations(nums2, nums1);
15 }
16
17 // Calculate the difference we need to eliminate
18 let difference = sum2 - sum1;
19
20 // Create an array to store the maximum change possible for each element
21 // For nums1: we can increase each element to 6 (maximum change: 6 - element)
22 // For nums2: we can decrease each element to 1 (maximum change: element - 1)
23 const maxChanges: number[] = [];
24
25 // Add maximum possible increases from nums1 (changing each to 6)
26 for (const value of nums1) {
27 maxChanges.push(6 - value);
28 }
29
30 // Add maximum possible decreases from nums2 (changing each to 1)
31 for (const value of nums2) {
32 maxChanges.push(value - 1);
33 }
34
35 // Sort the changes in descending order to use the largest changes first
36 maxChanges.sort((a, b) => b - a);
37
38 // Greedily apply the largest changes until the difference is eliminated
39 for (let i = 0; i < maxChanges.length; i++) {
40 difference -= maxChanges[i];
41 if (difference <= 0) {
42 return i + 1; // Return the number of operations performed
43 }
44 }
45
46 // If we cannot eliminate the difference, return -1
47 return -1;
48}
49
Time and Space Complexity
Time Complexity: O(n log n + m log m)
where n
is the length of nums1
and m
is the length of nums2
.
- Calculating the sums
s1
ands2
takesO(n) + O(m)
time. - Creating the
arr
list with list comprehensions takesO(n) + O(m)
time. - Sorting the combined array
arr
of sizen + m
takesO((n + m) log(n + m))
time. - The iteration through the sorted array takes at most
O(n + m)
time. - The recursive call (if triggered) doesn't add to the complexity as it only swaps the parameters once and won't recurse again.
- Overall, the dominant operation is sorting, giving us
O((n + m) log(n + m))
which simplifies toO(n log n + m log m)
whenn
andm
are of similar magnitude.
Space Complexity: O(n + m)
- The
arr
list storesn + m
elements, requiringO(n + m)
space. - The sorting operation may require
O(n + m)
additional space depending on the implementation. - The recursion depth is at most 1 (only one recursive call possible), adding
O(1)
to the call stack. - Therefore, the total space complexity is
O(n + m)
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Forgetting to Check if Equal Sums are Achievable
A critical pitfall is not verifying whether it's mathematically possible to make the sums equal before attempting the greedy approach. This can lead to incorrect results or unnecessary computation.
The Problem:
Consider extreme cases like nums1 = [1]
and nums2 = [6,6,6,6,6,6]
. Here:
sum(nums1) = 1
(can be at most 6 after changes)sum(nums2) = 36
(can be at least 6 after changes)
Even with all possible changes, the maximum achievable sum for nums1
is 6, while the minimum achievable sum for nums2
is 6. These ranges don't overlap sufficiently to reach equality.
The Solution: Add a feasibility check at the beginning:
class Solution:
def minOperations(self, nums1: List[int], nums2: List[int]) -> int:
# Check if it's possible to make sums equal
len1, len2 = len(nums1), len(nums2)
# Maximum possible sum for each array is length * 6
# Minimum possible sum for each array is length * 1
if 6 * len1 < len2 or 6 * len2 < len1:
return -1
# Rest of the original solution continues...
sum1, sum2 = sum(nums1), sum(nums2)
# ... (rest of the code)
2. Inefficient Array Swapping
The recursive call return self.minOperations(nums2, nums1)
when sum1 > sum2
creates unnecessary overhead and potential stack usage.
The Solution: Instead of recursion, simply swap the references:
class Solution:
def minOperations(self, nums1: List[int], nums2: List[int]) -> int:
sum1, sum2 = sum(nums1), sum(nums2)
if sum1 == sum2:
return 0
# Swap references instead of recursive call
if sum1 > sum2:
nums1, nums2 = nums2, nums1
sum1, sum2 = sum2, sum1
# Continue with the logic...
3. Not Handling Edge Cases with Empty or Single-Element Arrays
While the problem states arrays contain integers between 1 and 6, it's good practice to handle edge cases explicitly, especially when array lengths might be very different.
Enhanced Solution with All Fixes:
class Solution:
def minOperations(self, nums1: List[int], nums2: List[int]) -> int:
# Early feasibility check
len1, len2 = len(nums1), len(nums2)
if 6 * min(len1, len2) < max(len1, len2):
return -1
sum1, sum2 = sum(nums1), sum(nums2)
if sum1 == sum2:
return 0
# Ensure sum1 < sum2 without recursion
if sum1 > sum2:
nums1, nums2 = nums2, nums1
sum1, sum2 = sum2, sum1
# Calculate possible changes
max_changes = [6 - v for v in nums1] + [v - 1 for v in nums2]
difference = sum2 - sum1
# Greedily apply largest changes
for operation_count, change in enumerate(sorted(max_changes, reverse=True), 1):
difference -= change
if difference <= 0:
return operation_count
return -1
This enhanced version avoids recursion overhead, checks feasibility early to avoid unnecessary computation, and handles the problem more efficiently.
What's the output of running the following function using the following tree as input?
1def serialize(root):
2 res = []
3 def dfs(root):
4 if not root:
5 res.append('x')
6 return
7 res.append(root.val)
8 dfs(root.left)
9 dfs(root.right)
10 dfs(root)
11 return ' '.join(res)
12
1import java.util.StringJoiner;
2
3public static String serialize(Node root) {
4 StringJoiner res = new StringJoiner(" ");
5 serializeDFS(root, res);
6 return res.toString();
7}
8
9private static void serializeDFS(Node root, StringJoiner result) {
10 if (root == null) {
11 result.add("x");
12 return;
13 }
14 result.add(Integer.toString(root.val));
15 serializeDFS(root.left, result);
16 serializeDFS(root.right, result);
17}
18
1function serialize(root) {
2 let res = [];
3 serialize_dfs(root, res);
4 return res.join(" ");
5}
6
7function serialize_dfs(root, res) {
8 if (!root) {
9 res.push("x");
10 return;
11 }
12 res.push(root.val);
13 serialize_dfs(root.left, res);
14 serialize_dfs(root.right, res);
15}
16
Recommended Readings
Greedy Introduction div class responsive iframe iframe src https www youtube com embed WTslqPbj7I title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture web share allowfullscreen iframe div When do we use greedy Greedy algorithms tend to solve optimization problems Typically they will ask you to calculate the max min of some value Commonly you may see this phrased in the problem as max min longest shortest largest smallest etc These keywords can be identified by just scanning
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donβt Miss This!