LeetCode 1775 - Equal Sum Arrays With Minimum Number of Operations
The problem gives us two integer arrays, nums1 and nums2, where every element is between 1 and 6. In one operation, we may choose any element from either array and change it to any value from 1 to 6.
Difficulty: 🟡 Medium
Topics: Array, Hash Table, Greedy, Counting
Solution
Problem Understanding
The problem gives us two integer arrays, nums1 and nums2, where every element is between 1 and 6. In one operation, we may choose any element from either array and change it to any value from 1 to 6.
Our goal is to make the total sum of both arrays equal while using the minimum possible number of operations.
The important detail is that we are allowed to modify values in both arrays, and every operation can completely replace a number with any valid value. This means an element can contribute differently depending on whether we want to increase or decrease the total sum.
For example:
- Changing
1to6increases the sum by5 - Changing
6to1decreases the sum by5 - Changing
3to5increases the sum by2
The output should be the smallest number of operations needed to make the sums equal. If it is impossible, we return -1.
The constraints are large:
- Array lengths can each be up to
10^5 - Values are restricted to only
1through6
These constraints immediately tell us that exponential search or simulation of all possible modifications is impossible. We need an algorithm close to linear time.
An important observation is that each element has a maximum possible contribution toward reducing the difference between the sums:
- If we need to increase a sum, an element
xcan contribute at most6 - x - If we need to decrease a sum, an element
xcan contribute at mostx - 1
Several edge cases are important:
- The arrays may have very different lengths
- One array may already have the same sum as the other
- It may be mathematically impossible to equalize the sums
- One side may require many small operations instead of a few large ones
- Arrays can contain only
1s or only6s, limiting possible changes
A particularly important impossibility condition comes from the value limits. Since every number must remain between 1 and 6, there are hard limits on possible sums.
For an array of length n:
- Minimum possible sum is
n - Maximum possible sum is
6n
If the ranges of achievable sums for the two arrays do not overlap, equalization is impossible.
Approaches
Brute Force Approach
A brute force solution would try all possible modifications to elements in both arrays until the sums become equal.
One possible interpretation of brute force is:
- Generate every possible modified state
- Compute the sums
- Track the minimum operations needed
This quickly becomes infeasible because every element has 6 possible values. If the arrays contain n elements total, the search space becomes approximately:
6^n
Even for relatively small arrays, this is astronomically large.
Another less extreme brute force idea would repeatedly test every possible single modification and greedily choose one. However, without a structured strategy, this still becomes expensive because each operation might require scanning many possibilities repeatedly.
The brute force approach is too slow for arrays up to 10^5.
Key Insight for the Optimal Solution
The key insight is that we do not care about the exact final values of the arrays. We only care about reducing the difference between the sums as quickly as possible.
Suppose:
sum(nums1) < sum(nums2)
Then we need to:
- Increase elements in
nums1 - Decrease elements in
nums2
For every element, we can calculate its maximum possible contribution toward reducing the difference.
Examples:
-
In the smaller-sum array:
-
1can become6, contribution5 -
2can become6, contribution4 -
In the larger-sum array:
-
6can become1, contribution5 -
5can become1, contribution4
Once we compute all possible contributions, the optimal strategy becomes obvious:
Always use the largest available contribution first.
This is a classic greedy strategy. Since every operation has equal cost, namely one operation, choosing the operation that reduces the difference the most is always optimal.
Because values are only between 1 and 6, contributions are limited to 1 through 5, which makes counting extremely efficient.
| Approach | Time Complexity | Space Complexity | Notes |
|---|---|---|---|
| Brute Force | O(6^n) | O(6^n) | Explores huge numbers of possible states |
| Optimal | O(n) | O(1) | Greedy selection using contribution counting |
Algorithm Walkthrough
- Compute the sums of both arrays.
Let:
sum1 = sum(nums1)
sum2 = sum(nums2)
If the sums are already equal, return 0.
2. Ensure one array always represents the smaller sum.
If sum1 > sum2, swap the arrays and sums.
After this step:
sum1 <= sum2
Now we only need to think about increasing nums1 and decreasing nums2.
3. Compute the required difference.
diff = sum2 - sum1
This is how much we still need to eliminate. 4. Calculate all possible contributions.
For every number in nums1, the largest increase possible is:
6 - num
For every number in nums2, the largest decrease possible is:
num - 1
Each contribution tells us how much one operation could reduce the difference. 5. Count contributions by size.
Since contributions can only be 1 through 5, we store counts in a small array.
Example:
gain[5] = number of operations that can reduce diff by 5
gain[4] = number of operations that can reduce diff by 4
- Greedily use the largest contributions first.
Start from contribution 5 down to 1.
For each contribution value:
- Use as many operations as needed
- Reduce
diff - Increment operation count
We always prioritize larger contributions because each operation costs the same. 7. Stop once the difference becomes zero or negative.
At that point, we have equalized the sums.
8. If all contributions are exhausted and diff > 0, return -1.
Why it works
The greedy strategy is optimal because every operation has identical cost, namely one operation. Therefore, whenever we can reduce the remaining difference by a larger amount, doing so is always at least as good as using a smaller reduction.
If an optimal solution used a smaller contribution while a larger unused contribution existed, we could replace the smaller one with the larger one and achieve the same or better reduction using no additional operations. Therefore, choosing the largest available contribution first cannot make the solution worse, which proves the correctness of the greedy approach.
Python Solution
from typing import List
class Solution:
def minOperations(self, nums1: List[int], nums2: List[int]) -> int:
sum1 = sum(nums1)
sum2 = sum(nums2)
if sum1 == sum2:
return 0
# Ensure nums1 has the smaller sum
if sum1 > sum2:
nums1, nums2 = nums2, nums1
sum1, sum2 = sum2, sum1
diff = sum2 - sum1
# gain[i] = how many operations can reduce diff by i
gain = [0] * 6
# Increasing nums1 elements
for num in nums1:
gain[6 - num] += 1
# Decreasing nums2 elements
for num in nums2:
gain[num - 1] += 1
operations = 0
# Greedily use biggest gains first
for value in range(5, 0, -1):
while gain[value] > 0 and diff > 0:
diff -= value
gain[value] -= 1
operations += 1
return operations if diff <= 0 else -1
The implementation begins by computing the sums of both arrays. If the sums are already equal, the answer is immediately 0.
Next, the code ensures that nums1 always has the smaller sum. This simplifies the rest of the logic because we only need to think about increasing values in nums1 and decreasing values in nums2.
The variable diff stores the remaining difference between the sums.
The gain array is the core optimization. Since every operation can reduce the difference by at most 5, we only need six slots indexed from 0 to 5.
For every value in nums1, the code calculates how much the value could increase:
6 - num
For every value in nums2, the code calculates how much the value could decrease:
num - 1
These possible reductions are counted in gain.
The final loop processes gains from 5 down to 1. This implements the greedy strategy of always using the most powerful operation first.
As soon as diff <= 0, the sums can be made equal, so the algorithm returns the number of operations used.
Go Solution
package main
func minOperations(nums1 []int, nums2 []int) int {
sum1 := 0
sum2 := 0
for _, num := range nums1 {
sum1 += num
}
for _, num := range nums2 {
sum2 += num
}
if sum1 == sum2 {
return 0
}
// Ensure nums1 has the smaller sum
if sum1 > sum2 {
nums1, nums2 = nums2, nums1
sum1, sum2 = sum2, sum1
}
diff := sum2 - sum1
// gain[i] = count of operations reducing diff by i
gain := make([]int, 6)
// Increase nums1
for _, num := range nums1 {
gain[6-num]++
}
// Decrease nums2
for _, num := range nums2 {
gain[num-1]++
}
operations := 0
for value := 5; value >= 1; value-- {
for gain[value] > 0 && diff > 0 {
diff -= value
gain[value]--
operations++
}
}
if diff > 0 {
return -1
}
return operations
}
The Go implementation follows the same logic as the Python solution. Slices are used for the gain frequency array, and range loops simplify iteration over the arrays.
Go does not require any special handling for integer overflow here because the maximum possible sum is:
6 * 10^5
which easily fits within Go's int type.
Unlike Python, Go does not have built in sum functions, so the sums are computed manually with loops.
Worked Examples
Example 1
nums1 = [1,2,3,4,5,6]
nums2 = [1,1,2,2,2,2]
Initial sums:
| Array | Sum |
|---|---|
| nums1 | 21 |
| nums2 | 10 |
Since nums1 has the larger sum, swap them.
| Smaller Sum Array | Larger Sum Array |
|---|---|
| [1,1,2,2,2,2] | [1,2,3,4,5,6] |
Now:
diff = 21 - 10 = 11
Possible gains from smaller array:
| Value | Gain |
|---|---|
| 1 | 5 |
| 1 | 5 |
| 2 | 4 |
| 2 | 4 |
| 2 | 4 |
| 2 | 4 |
Possible gains from larger array:
| Value | Gain |
|---|---|
| 1 | 0 |
| 2 | 1 |
| 3 | 2 |
| 4 | 3 |
| 5 | 4 |
| 6 | 5 |
Gain frequencies:
| Gain | Count |
|---|---|
| 5 | 3 |
| 4 | 5 |
| 3 | 1 |
| 2 | 1 |
| 1 | 1 |
Greedy process:
| Operation | Gain Used | Remaining Diff |
|---|---|---|
| 1 | 5 | 6 |
| 2 | 5 | 1 |
| 3 | 4 | -3 |
Answer:
3
Example 2
nums1 = [1,1,1,1,1,1,1]
nums2 = [6]
Sums:
| Array | Sum |
|---|---|
| nums1 | 7 |
| nums2 | 6 |
Swap arrays:
| Smaller | Larger |
|---|---|
| [6] | [1,1,1,1,1,1,1] |
Difference:
diff = 1
Possible gains:
6 -> 1gives gain5- Every
1in larger array gives gain0
Only one useful operation exists.
But after processing all possible changes, the achievable ranges do not overlap correctly because the larger array cannot be reduced enough and the smaller array cannot be increased enough.
The algorithm eventually exhausts all useful gains and returns:
-1
Example 3
nums1 = [6,6]
nums2 = [1]
Sums:
| Array | Sum |
|---|---|
| nums1 | 12 |
| nums2 | 1 |
Swap arrays:
| Smaller | Larger |
|---|---|
| [1] | [6,6] |
Difference:
diff = 11
Possible gains:
| Operation | Gain |
|---|---|
| 1 -> 6 | 5 |
| 6 -> 1 | 5 |
| 6 -> 1 | 5 |
Greedy execution:
| Operation | Gain Used | Remaining Diff |
|---|---|---|
| 1 | 5 | 6 |
| 2 | 5 | 1 |
| 3 | 5 | -4 |
Total operations:
3
Complexity Analysis
| Measure | Complexity | Explanation |
|---|---|---|
| Time | O(n) | Each element is processed a constant number of times |
| Space | O(1) | The gain array has fixed size 6 |
The algorithm scans both arrays once to compute sums and once more to compute contribution frequencies. The greedy loop iterates only over gain values from 5 down to 1, which is constant work.
Although the total number of operations may conceptually be large, each possible contribution is processed once through frequency counting, keeping the runtime linear in the total input size.
The extra memory usage is constant because the gain array always contains exactly six entries regardless of input size.
Test Cases
from typing import List
class Solution:
def minOperations(self, nums1: List[int], nums2: List[int]) -> int:
sum1 = sum(nums1)
sum2 = sum(nums2)
if sum1 == sum2:
return 0
if sum1 > sum2:
nums1, nums2 = nums2, nums1
sum1, sum2 = sum2, sum1
diff = sum2 - sum1
gain = [0] * 6
for num in nums1:
gain[6 - num] += 1
for num in nums2:
gain[num - 1] += 1
operations = 0
for value in range(5, 0, -1):
while gain[value] > 0 and diff > 0:
diff -= value
gain[value] -= 1
operations += 1
return operations if diff <= 0 else -1
sol = Solution()
assert sol.minOperations([1,2,3,4,5,6], [1,1,2,2,2,2]) == 3 # provided example
assert sol.minOperations([1,1,1,1,1,1,1], [6]) == -1 # impossible case
assert sol.minOperations([6,6], [1]) == 3 # provided example
assert sol.minOperations([1], [1]) == 0 # already equal
assert sol.minOperations([1], [6]) == 1 # single operation enough
assert sol.minOperations([1,1,1], [6,6,6]) == 3 # maximum gains needed
assert sol.minOperations([5,6,6], [1,1,1]) == 3 # requires multiple decreases
assert sol.minOperations([2,3,4], [2,3,4]) == 0 # identical arrays
assert sol.minOperations([1]*100000, [6]*100000) == 100000 # large stress case
assert sol.minOperations([1,2], [6,6]) == 2 # mixed operations
assert sol.minOperations([6], [1,1,1,1,1,1]) == 1 # one large increase
| Test | Why |
|---|---|
[1,2,3,4,5,6] vs [1,1,2,2,2,2] |
Validates standard greedy behavior |
[1,1,1,1,1,1,1] vs [6] |
Validates impossible scenario |
[6,6] vs [1] |
Tests repeated maximum reductions |
[1] vs [1] |
Tests already equal sums |
[1] vs [6] |
Tests single-operation adjustment |
[1,1,1] vs [6,6,6] |
Tests multiple maximum-gain operations |
[5,6,6] vs [1,1,1] |
Tests heavy decreasing operations |
[2,3,4] vs [2,3,4] |
Tests equal arrays |
Large arrays of 1s and 6s |
Stress test for performance |
[1,2] vs [6,6] |
Tests mixed contribution sizes |
[6] vs [1,1,1,1,1,1] |
Tests large gain from a single element |
Edge Cases
One important edge case occurs when the sums are already equal at the beginning. A careless implementation might continue processing unnecessarily or even produce an incorrect positive answer. The implementation handles this immediately with:
if sum1 == sum2:
return 0
Another important edge case is when equalization is impossible. This happens when the total achievable ranges of the arrays do not overlap. For example:
nums1 = [1,1,1,1,1,1,1]
nums2 = [6]
Even after all possible changes, the sums can never become equal. The implementation naturally detects this because all possible gains are exhausted while diff remains positive, causing the function to return -1.
A third critical edge case involves extremely unbalanced arrays with very large input sizes. Since the constraints allow arrays up to 10^5 elements, any quadratic solution would time out. The implementation avoids this by using a fixed-size counting array and processing each element only a constant number of times.
Another subtle edge case occurs when many elements contribute zero gain. For example:
nums1 = [6,6,6]
nums2 = [1,1,1]
If we are trying to increase nums1, its elements already equal 6, so they contribute nothing. Similarly, 1s cannot be decreased further. The implementation handles this correctly because zero-gain operations are ignored naturally through the frequency counting process.
Finally, it is important that the algorithm always processes the larger contributions first. If smaller contributions were used too early, the solution might use more operations than necessary. The descending greedy loop guarantees that the minimum number of operations is always found.