1769. Minimum Number of Operations to Move All Balls to Each Box - Explanation
Description
You have n boxes. You are given a binary string boxes of length n, where boxes[i] is '0' if the i-th box is empty, and '1' if it contains one ball.
In one operation, you can move one ball from a box to an adjacent box. Box i is adjacent to box j if abs(i - j) == 1. Note that after doing so, there may be more than one ball in some boxes.
Return an array answer of size n, where answer[i] is the minimum number of operations needed to move all the balls to the i-th box.
Each answer[i] is calculated considering the initial state of the boxes.
Example 1:
Input: boxes = "110"
Output: [1,1,3]Explanation: The answer for each box is as follows:
- First box: you will have to move one ball from the second box to the first box in one operation.
- Second box: you will have to move one ball from the first box to the second box in one operation.
- Third box: you will have to move one ball from the first box to the third box in two operations, and move one ball from the second box to the third box in one operation.
Example 2:
Input: boxes = "001011"
Output: [11,8,5,4,3,4]Constraints:
n == boxes.length1 <= n <= 2000boxes[i]is either'0'or'1'.
Topics
Prerequisites
Before attempting this problem, you should be comfortable with:
- Array Traversal - Understanding how to iterate through arrays and compute cumulative values
- Prefix Sums - The optimized solution uses prefix sums to efficiently calculate contributions from left and right
- Two-Pass Technique - The optimal approach processes the array in two passes (left-to-right and right-to-left)
1. Brute Force
Intuition
For each box position, we want to know the total number of moves needed to bring all balls to that position. A move shifts a ball one position left or right. The cost to move a ball from position i to position pos is simply the absolute difference |pos - i|.
We can compute this directly by iterating through all boxes for each target position and summing up the distances from each ball.
Algorithm
- Create a
resarray of sizen. - For each target position
pos:- Iterate through all boxes.
- If box
icontains aball(value is '1'), add|pos - i|tores.
- Return the
resarray.
Time & Space Complexity
- Time complexity:
- Space complexity:
- extra space.
- space for the output list.
2. Prefix Sum
Intuition
We can split the total cost for each position into contributions from the left and the right. For balls on the left, the cost is i * count_left - sum_of_indices_left. For balls on the right, the cost is sum_of_indices_right - i * count_right. Using prefix sums for both the count of balls and the sum of their indices, we can compute both parts efficiently.
Algorithm
- Build two prefix arrays:
prefix_count[i]= number ofballsin boxes 0 toi-1.index_sum[i]= sum ofindicesofballsin boxes 0 toi-1.
- For each position
i:- Left contribution:
i * left_count - left_sum. - Right contribution:
right_sum - i * right_count. - Add both to get the
resfor positioni.
- Left contribution:
- Return the
resarray.
class Solution:
def minOperations(self, boxes: str) -> List[int]:
n = len(boxes)
res = [0] * n
prefix_count = [0] * (n + 1)
index_sum = [0] * (n + 1)
for i in range(n):
prefix_count[i + 1] = prefix_count[i] + (boxes[i] == '1')
index_sum[i + 1] = index_sum[i] + (i if boxes[i] == '1' else 0)
for i in range(n):
left = prefix_count[i]
left_sum = index_sum[i]
right = prefix_count[n] - prefix_count[i + 1]
right_sum = index_sum[n] - index_sum[i + 1]
res[i] = (i * left - left_sum) + (right_sum - i * right)
return resTime & Space Complexity
- Time complexity:
- Space complexity:
3. Prefix Sum (Optimal)
Intuition
Instead of storing prefix arrays, we can compute the contribution incrementally using two passes. In the left-to-right pass, we track how many balls are to the left and accumulate the moves needed to shift them one position right. In the right-to-left pass, we do the same for balls on the right. The sum of both passes gives the final answer.
Algorithm
- Left-to-right pass:
- Track
balls(count ofballsseen) andmoves(cumulative operations). - For each position,
res[i] = balls + moves, then updatemoves += ballsand add currentballif present.
- Track
- Right-to-left pass:
- Reset
ballsandmoves. - For each position from right to left, add
balls + movestores[i], then update similarly.
- Reset
- Return the
resarray.
class Solution:
def minOperations(self, boxes: str) -> List[int]:
n = len(boxes)
res = [0] * n
balls = moves = 0
for i in range(n):
res[i] = balls + moves
moves += balls
balls += int(boxes[i])
balls = moves = 0
for i in range(n - 1, -1, -1):
res[i] += balls + moves
moves += balls
balls += int(boxes[i])
return resTime & Space Complexity
- Time complexity:
- Space complexity:
- extra space.
- space for the output list.
Common Pitfalls
Character vs Integer Comparison
The input is a string where boxes contain '0' or '1' as characters, not integers. Comparing boxes[i] == 1 instead of boxes[i] == '1' will always evaluate to false in most languages, causing the algorithm to miss all balls.
Incorrect Order of Operations in Two-Pass
In the optimal prefix sum approach, the order of updating balls, moves, and res[i] matters. Adding the current ball before calculating the result will cause off-by-one errors where the ball at position i incorrectly contributes to its own cost.
Off-by-One in Left/Right Contribution Formula
When using the formula i * leftCount - leftSum for left contribution, using i + 1 or indexing errors in prefix arrays can shift all calculations. Carefully verify that prefix arrays are 0-indexed or 1-indexed and adjust formulas accordingly.
Sign in to join the discussion