Facebook Pixel

1769. Minimum Number of Operations to Move All Balls to Each Box

Problem Description

You are given a string boxes of length n consisting of '0's and '1's. Each character represents a box, where '0' means the box is empty and '1' means the box contains one ball.

You can perform operations to move balls between boxes. In one operation, you can move one ball from a box to an adjacent box (a box that is exactly one position away, either to the left or right).

Your task is to calculate, for each box position i, the minimum total number of operations needed to move all balls in the string to that specific box i. Return an array answer where answer[i] represents this minimum number of operations for box i.

For example, if boxes = "110", there are balls at positions 0 and 1:

  • To move all balls to position 0: move the ball from position 1 to position 0 (1 operation)
  • To move all balls to position 1: move the ball from position 0 to position 1 (1 operation)
  • To move all balls to position 2: move the ball from position 0 to position 1, then to position 2 (2 operations), and move the ball from position 1 to position 2 (1 operation), totaling 3 operations

The result would be [1, 1, 3].

Note that multiple balls can end up in the same box after operations, and each calculation starts from the initial configuration of boxes.

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

Intuition

To move all balls to a specific position i, we need to calculate the total distance each ball must travel. A ball at position j needs |i - j| operations to reach position i.

The key insight is that we can split this calculation into two parts:

  • Balls to the left of position i need to move right
  • Balls to the right of position i need to move left

For each position i, instead of recalculating from scratch, we can use dynamic programming to build upon previous calculations.

Consider moving from position i to position i+1. All balls that were to the left of position i are now one step further away from position i+1. If there are cnt balls to the left, the total cost increases by cnt. This observation allows us to maintain a running sum.

We can precompute two arrays:

  • left[i]: total operations needed to move all balls from positions 0 to i-1 to position i
  • right[i]: total operations needed to move all balls from positions i+1 to n-1 to position i

For left[i], we accumulate the cost as we move right. If we know left[i-1] (cost to move all balls to the left of i-1 to position i-1), then left[i] = left[i-1] + cnt, where cnt is the number of balls to the left of position i.

Similarly, for right[i], we work backwards from right to left.

The final answer for each position i is simply left[i] + right[i], combining the costs from both directions.

Learn more about Prefix Sum patterns.

Solution Approach

The solution uses dynamic programming with two auxiliary arrays to efficiently compute the minimum operations for each position.

Step 1: Initialize arrays

  • Create two arrays left and right, both of size n, initialized with zeros
  • left[i] will store the total operations to move all balls from positions 0 to i-1 to position i
  • right[i] will store the total operations to move all balls from positions i+1 to n-1 to position i

Step 2: Build the left array

  • Start from position 1 and iterate to the right
  • Maintain a counter cnt for the number of balls seen so far to the left
  • For each position i:
    • If boxes[i-1] == '1', increment cnt (found a ball at position i-1)
    • Calculate left[i] = left[i-1] + cnt
    • This works because moving from position i-1 to i, all cnt balls to the left need one additional operation

Step 3: Build the right array

  • Start from position n-2 and iterate to the left
  • Reset counter cnt to track balls to the right
  • For each position i:
    • If boxes[i+1] == '1', increment cnt (found a ball at position i+1)
    • Calculate right[i] = right[i+1] + cnt
    • This accumulates the cost of moving all balls from the right to position i

Step 4: Combine results

  • For each position i, the total minimum operations is left[i] + right[i]
  • Return the combined array using list comprehension: [a + b for a, b in zip(left, right)]

Time Complexity: O(n) - We make two passes through the array Space Complexity: O(n) - We use two auxiliary arrays of size n

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 the solution with boxes = "110":

Initial Setup:

  • Input: boxes = "110" (balls at positions 0 and 1)
  • Create arrays: left = [0, 0, 0] and right = [0, 0, 0]

Step 1: Build the left array

Starting from position 1, track balls to the left:

  • Position 1:

    • Check boxes[0] = '1', so cnt = 1 (one ball to the left)
    • left[1] = left[0] + cnt = 0 + 1 = 1
    • This means moving 1 ball from position 0 to position 1 takes 1 operation
  • Position 2:

    • Check boxes[1] = '1', so cnt = 2 (two balls to the left now)
    • left[2] = left[1] + cnt = 1 + 2 = 3
    • This means moving both balls to position 2 takes 3 total operations
    • (Ball at position 0 travels 2 steps = 2 ops, ball at position 1 travels 1 step = 1 op)

Result: left = [0, 1, 3]

Step 2: Build the right array

Starting from position 1 (going backwards), track balls to the right:

  • Position 1:

    • Check boxes[2] = '0', so cnt = 0 (no balls to the right)
    • right[1] = right[2] + cnt = 0 + 0 = 0
  • Position 0:

    • Check boxes[1] = '1', so cnt = 1 (one ball to the right)
    • right[0] = right[1] + cnt = 0 + 1 = 1
    • This means moving 1 ball from position 1 to position 0 takes 1 operation

Result: right = [1, 0, 0]

Step 3: Combine results

For each position, add left and right costs:

  • Position 0: left[0] + right[0] = 0 + 1 = 1
  • Position 1: left[1] + right[1] = 1 + 0 = 1
  • Position 2: left[2] + right[2] = 3 + 0 = 3

Final Answer: [1, 1, 3]

This matches our expected result, where moving all balls to positions 0, 1, and 2 requires 1, 1, and 3 operations respectively.

Solution Implementation

1class Solution:
2    def minOperations(self, boxes: str) -> List[int]:
3        """
4        Calculate minimum operations to move all balls to each position.
5      
6        Args:
7            boxes: String where '1' represents a ball and '0' represents empty box
8          
9        Returns:
10            List where result[i] is the minimum operations to move all balls to position i
11        """
12        n = len(boxes)
13      
14        # left_cost[i]: total cost to move all balls from left side (0 to i-1) to position i
15        left_cost = [0] * n
16      
17        # right_cost[i]: total cost to move all balls from right side (i+1 to n-1) to position i
18        right_cost = [0] * n
19      
20        # Calculate left side costs
21        # balls_count tracks number of balls seen so far from the left
22        balls_count = 0
23        for i in range(1, n):
24            # If previous position has a ball, increment the count
25            if boxes[i - 1] == '1':
26                balls_count += 1
27            # Cost at position i = cost at position i-1 + number of balls that need to move one more step
28            left_cost[i] = left_cost[i - 1] + balls_count
29      
30        # Calculate right side costs
31        # Reset balls_count for right side calculation
32        balls_count = 0
33        for i in range(n - 2, -1, -1):
34            # If next position has a ball, increment the count
35            if boxes[i + 1] == '1':
36                balls_count += 1
37            # Cost at position i = cost at position i+1 + number of balls that need to move one more step
38            right_cost[i] = right_cost[i + 1] + balls_count
39      
40        # Total cost for each position is sum of left and right costs
41        return [left + right for left, right in zip(left_cost, right_cost)]
42
1class Solution {
2    public int[] minOperations(String boxes) {
3        int n = boxes.length();
4      
5        // Array to store cumulative moves needed from left side to position i
6        int[] leftMoves = new int[n];
7      
8        // Array to store cumulative moves needed from right side to position i
9        int[] rightMoves = new int[n];
10      
11        // Calculate moves needed from left side
12        // ballCount tracks number of balls encountered so far from the left
13        int ballCount = 0;
14        for (int i = 1; i < n; i++) {
15            // If previous position had a ball, increment ball count
16            if (boxes.charAt(i - 1) == '1') {
17                ballCount++;
18            }
19            // Total moves = previous moves + (number of balls * 1 step each)
20            leftMoves[i] = leftMoves[i - 1] + ballCount;
21        }
22      
23        // Calculate moves needed from right side
24        // Reset ballCount for right side calculation
25        ballCount = 0;
26        for (int i = n - 2; i >= 0; i--) {
27            // If next position has a ball, increment ball count
28            if (boxes.charAt(i + 1) == '1') {
29                ballCount++;
30            }
31            // Total moves = previous moves + (number of balls * 1 step each)
32            rightMoves[i] = rightMoves[i + 1] + ballCount;
33        }
34      
35        // Combine left and right moves to get total moves for each position
36        int[] result = new int[n];
37        for (int i = 0; i < n; i++) {
38            result[i] = leftMoves[i] + rightMoves[i];
39        }
40      
41        return result;
42    }
43}
44
1class Solution {
2public:
3    vector<int> minOperations(string boxes) {
4        int n = boxes.size();
5      
6        // Arrays to store cumulative costs from left and right
7        vector<int> leftCost(n, 0);
8        vector<int> rightCost(n, 0);
9      
10        // Calculate cumulative cost from left to right
11        // leftCost[i] = total moves needed to move all balls from positions 0 to i-1 to position i
12        int ballsOnLeft = 0;
13        for (int i = 1; i < n; ++i) {
14            // Add a ball if there was one at the previous position
15            if (boxes[i - 1] == '1') {
16                ballsOnLeft++;
17            }
18            // Each ball on the left needs one additional move to reach current position
19            leftCost[i] = leftCost[i - 1] + ballsOnLeft;
20        }
21      
22        // Calculate cumulative cost from right to left
23        // rightCost[i] = total moves needed to move all balls from positions i+1 to n-1 to position i
24        int ballsOnRight = 0;
25        for (int i = n - 2; i >= 0; --i) {
26            // Add a ball if there was one at the next position
27            if (boxes[i + 1] == '1') {
28                ballsOnRight++;
29            }
30            // Each ball on the right needs one additional move to reach current position
31            rightCost[i] = rightCost[i + 1] + ballsOnRight;
32        }
33      
34        // Combine left and right costs to get total operations for each position
35        vector<int> result(n);
36        for (int i = 0; i < n; ++i) {
37            result[i] = leftCost[i] + rightCost[i];
38        }
39      
40        return result;
41    }
42};
43
1/**
2 * Calculates minimum operations to move all balls to each position
3 * @param boxes - String where '1' represents a ball and '0' represents empty
4 * @returns Array where each element represents total operations to move all balls to that position
5 */
6function minOperations(boxes: string): number[] {
7    const boxCount: number = boxes.length;
8  
9    // Array to store cumulative operations from left side
10    const leftOperations: number[] = new Array(boxCount).fill(0);
11  
12    // Array to store cumulative operations from right side
13    const rightOperations: number[] = new Array(boxCount).fill(0);
14  
15    // Calculate operations needed to move balls from left to each position
16    // ballsOnLeft tracks the number of balls encountered so far
17    for (let i = 1, ballsOnLeft = 0; i < boxCount; i++) {
18        // If previous position had a ball, increment the count
19        if (boxes[i - 1] === '1') {
20            ballsOnLeft++;
21        }
22        // Each ball on the left needs one more step to reach current position
23        leftOperations[i] = leftOperations[i - 1] + ballsOnLeft;
24    }
25  
26    // Calculate operations needed to move balls from right to each position
27    // ballsOnRight tracks the number of balls encountered so far
28    for (let i = boxCount - 2, ballsOnRight = 0; i >= 0; i--) {
29        // If next position had a ball, increment the count
30        if (boxes[i + 1] === '1') {
31            ballsOnRight++;
32        }
33        // Each ball on the right needs one more step to reach current position
34        rightOperations[i] = rightOperations[i + 1] + ballsOnRight;
35    }
36  
37    // Combine left and right operations for total operations at each position
38    return leftOperations.map((leftOps, index) => leftOps + rightOperations[index]);
39}
40

Time and Space Complexity

Time Complexity: O(n), where n is the length of the boxes string.

The algorithm consists of three main parts:

  1. First forward pass from index 1 to n-1: O(n) iterations
  2. Second backward pass from index n-2 to 0: O(n) iterations
  3. Final list comprehension to combine left and right arrays: O(n) iterations

Each iteration performs constant time operations (comparisons, additions, array assignments). Therefore, the total time complexity is O(n) + O(n) + O(n) = O(n).

Space Complexity: O(n)

The algorithm uses:

  • left array of size n: O(n) space
  • right array of size n: O(n) space
  • The output list (result of zip operation) of size n: O(n) space
  • Two counter variables cnt: O(1) space

The total auxiliary space used is O(n) + O(n) = O(n). Note that if we don't count the output array as extra space (as it's required for the answer), the space complexity remains O(n) due to the left and right arrays.

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

Common Pitfalls

1. Off-by-One Error in Ball Counting

Pitfall: A common mistake is incorrectly tracking when to count balls. Developers often confuse whether to check boxes[i] or boxes[i-1] when building the left array, and similarly for the right array.

Why it happens: The confusion arises because we're calculating the cost for position i, but we need to count balls that are NOT at position i itself.

Incorrect approach:

# WRONG: Checking current position instead of previous
for i in range(1, n):
    if boxes[i] == '1':  # This is wrong!
        balls_count += 1
    left_cost[i] = left_cost[i - 1] + balls_count

Correct approach:

# CORRECT: Check previous position for left array
for i in range(1, n):
    if boxes[i - 1] == '1':  # Check the position we're moving FROM
        balls_count += 1
    left_cost[i] = left_cost[i - 1] + balls_count

2. Incorrect Initialization or Range Boundaries

Pitfall: Starting the loops from wrong indices or using incorrect range boundaries, especially for the right array calculation.

Incorrect approach:

# WRONG: Starting from wrong index or going to wrong boundary
for i in range(n - 1, 0, -1):  # Missing index 0!
    if boxes[i + 1] == '1':
        balls_count += 1
    right_cost[i] = right_cost[i + 1] + balls_count

Correct approach:

# CORRECT: Process from n-2 down to 0 (inclusive)
for i in range(n - 2, -1, -1):
    if boxes[i + 1] == '1':
        balls_count += 1
    right_cost[i] = right_cost[i + 1] + balls_count

3. Reusing the Same Counter Variable

Pitfall: Forgetting to reset the balls_count variable between calculating left and right arrays.

Incorrect approach:

balls_count = 0
# Calculate left costs
for i in range(1, n):
    if boxes[i - 1] == '1':
        balls_count += 1
    left_cost[i] = left_cost[i - 1] + balls_count

# WRONG: Forgot to reset balls_count!
# balls_count still has value from left calculation
for i in range(n - 2, -1, -1):
    if boxes[i + 1] == '1':
        balls_count += 1
    right_cost[i] = right_cost[i + 1] + balls_count

Solution: Always reset the counter before starting the second loop:

balls_count = 0  # Reset before calculating right costs

4. Misunderstanding the Problem Statement

Pitfall: Thinking that balls disappear after moving them or that we need to track individual ball movements rather than calculating the total distance.

Key insight: Each position's answer is independent - we always start from the original configuration and calculate the total operations needed to move ALL balls to that specific position. The balls don't actually move between calculations.

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

Consider the classic dynamic programming of longest increasing subsequence:

Find the length of the longest subsequence of a given sequence such that all elements of the subsequence are sorted in increasing order.

For example, the length of LIS for [50, 3, 10, 7, 40, 80] is 4 and LIS is [3, 7, 40, 80].

What is the recurrence relation?


Recommended Readings

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

Load More