Facebook Pixel

2938. Separate Black and White Balls

Problem Description

You have n balls arranged in a row on a table. Each ball is either black or white.

You're given a binary string s of length n where:

  • '1' represents a black ball
  • '0' represents a white ball
  • The string is 0-indexed (positions are numbered from 0 to n-1)

In each step, you can swap two adjacent balls.

Your goal is to arrange all the balls so that:

  • All white balls ('0') are on the left side
  • All black balls ('1') are on the right side

Return the minimum number of steps (swaps) needed to achieve this arrangement.

For example, if s = "101", you would need to swap the middle '0' with the rightmost '1' to get "011", which takes 1 step.

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

Intuition

The key insight is that we want all '1's (black balls) to end up on the right side. Instead of simulating actual swaps, we can count how many positions each '1' needs to move to reach its final position.

Think about it this way: if we process the string from right to left, we can keep track of how many '1's we've already "placed" on the right. When we encounter a new '1', we know exactly where it should go - it should be placed just before all the '1's we've already seen.

For a '1' at position i, if we've already placed cnt number of '1's on the right, then this '1' should ultimately be at position n - cnt - 1 (where n is the length of string). The number of steps needed to move it there is (n - cnt - 1) - i = n - i - cnt - 1.

However, since we're counting from position i and there are already cnt balls to its right that are '1's, and we increment cnt before calculating, the formula becomes n - i - cnt.

By traversing from right to left and accumulating these distances for each '1', we get the total minimum number of swaps needed. This works because each '1' independently needs to move a certain distance to the right, and these movements don't interfere with each other when counted this way.

Learn more about Greedy and Two Pointers patterns.

Solution Approach

We implement the solution by traversing the string from right to left and counting the moves needed for each '1' to reach its final position.

Here's the step-by-step implementation:

  1. Initialize variables:

    • n = len(s): Store the length of the string
    • ans = 0: Accumulator for the total number of moves
    • cnt = 0: Counter for the number of '1's we've seen so far from the right
  2. Traverse from right to left:

    for i in range(n - 1, -1, -1):

    We iterate through the string backwards, from index n-1 to 0.

  3. Process each '1':

    if s[i] == '1':
        cnt += 1
        ans += n - i - cnt

    When we encounter a '1' at position i:

    • First, increment cnt by 1 (we've found another '1')
    • Calculate the distance this '1' needs to move: n - i - cnt
      • n - cnt would be the target position (counting from 1)
      • i is the current position (counting from 0)
      • So the distance is (n - cnt) - (i + 1) = n - i - cnt
    • Add this distance to our total ans
  4. Return the result: After processing all positions, ans contains the minimum number of swaps needed.

The algorithm runs in O(n) time with O(1) extra space, making it highly efficient. Each '1' is processed exactly once, and we calculate its contribution to the total moves in constant time.

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 trace through the algorithm with s = "10101".

Initial Setup:

  • n = 5 (length of string)
  • ans = 0 (total moves counter)
  • cnt = 0 (count of '1's seen from right)
  • Final goal: All '0's on left, all '1's on right โ†’ "00111"

Step-by-step traversal (right to left):

i = 4: s[4] = '1'

  • This is a '1', so we process it
  • cnt = 1 (increment count)
  • This '1' needs to move to position: n - cnt = 5 - 1 = 4
  • Current position is 4, so moves needed: n - i - cnt = 5 - 4 - 1 = 0
  • ans = 0 + 0 = 0

i = 3: s[3] = '0'

  • This is a '0', skip it (it will naturally move left)
  • cnt = 1, ans = 0

i = 2: s[2] = '1'

  • This is a '1', so we process it
  • cnt = 2 (increment count)
  • This '1' needs to move to position: n - cnt = 5 - 2 = 3
  • Current position is 2, so moves needed: n - i - cnt = 5 - 2 - 2 = 1
  • ans = 0 + 1 = 1

i = 1: s[1] = '0'

  • This is a '0', skip it
  • cnt = 2, ans = 1

i = 0: s[0] = '1'

  • This is a '1', so we process it
  • cnt = 3 (increment count)
  • This '1' needs to move to position: n - cnt = 5 - 3 = 2
  • Current position is 0, so moves needed: n - i - cnt = 5 - 0 - 3 = 2
  • ans = 1 + 2 = 3

Result: ans = 3

Verification: To transform "10101" โ†’ "00111":

  • Move '1' at index 0 past two '0's (2 swaps): "01101" โ†’ "01011"
  • Move '1' at index 2 past one '0' (1 swap): "01011" โ†’ "00111"
  • Total: 3 swaps โœ“

Solution Implementation

1class Solution:
2    def minimumSteps(self, s: str) -> int:
3        """
4        Calculate minimum steps to move all '1's to the right of all '0's.
5      
6        Args:
7            s: Binary string containing only '0' and '1' characters
8          
9        Returns:
10            Minimum number of steps required
11        """
12        string_length = len(s)
13        total_steps = 0
14        ones_count = 0
15      
16        # Traverse the string from right to left
17        for current_index in range(string_length - 1, -1, -1):
18            if s[current_index] == '1':
19                # Found a '1' that needs to be moved to the right
20                ones_count += 1
21              
22                # Calculate steps needed to move this '1' to its target position
23                # Target position is (string_length - ones_count)
24                # Current position is current_index
25                # Steps needed = target_position - current_position
26                steps_for_this_one = (string_length - ones_count) - current_index
27                total_steps += steps_for_this_one
28      
29        return total_steps
30
1class Solution {
2    public long minimumSteps(String s) {
3        long totalSteps = 0;
4        int onesCount = 0;  // Count of '1's encountered so far from right to left
5        int stringLength = s.length();
6      
7        // Traverse the string from right to left
8        for (int currentIndex = stringLength - 1; currentIndex >= 0; currentIndex--) {
9            // If we encounter a '1', it needs to move past all '0's to its right
10            if (s.charAt(currentIndex) == '1') {
11                onesCount++;
12                // Calculate how many positions this '1' needs to move right
13                // Target position for this '1' is (stringLength - onesCount)
14                // Current position is currentIndex
15                // Steps needed = target position - current position
16                totalSteps += (stringLength - currentIndex - onesCount);
17            }
18        }
19      
20        return totalSteps;
21    }
22}
23
1class Solution {
2public:
3    long long minimumSteps(string s) {
4        long long totalSteps = 0;  // Total number of steps/swaps needed
5        int onesCount = 0;          // Count of '1's encountered from the right
6        int stringLength = s.size();
7      
8        // Traverse the string from right to left
9        for (int i = stringLength - 1; i >= 0; --i) {
10            if (s[i] == '1') {
11                // Found a '1' that needs to move to the right
12                ++onesCount;
13              
14                // Calculate steps needed to move this '1' to its final position
15                // Final position would be (stringLength - onesCount)
16                // Current position is i
17                // Steps needed = final position - current position
18                totalSteps += (stringLength - i - onesCount);
19            }
20        }
21      
22        return totalSteps;
23    }
24};
25
1/**
2 * Calculates the minimum number of steps to move all '1's to the right side of the string.
3 * The algorithm works by traversing from right to left, counting '1's and calculating
4 * the distance each '1' needs to move to reach its final position.
5 * 
6 * @param s - Binary string containing only '0's and '1's
7 * @returns The minimum number of steps required
8 */
9function minimumSteps(s: string): number {
10    const stringLength: number = s.length;
11    let totalSteps: number = 0;
12    let onesEncountered: number = 0;
13  
14    // Traverse the string from right to left
15    for (let index: number = stringLength - 1; index >= 0; index--) {
16        if (s[index] === '1') {
17            // Increment count of '1's encountered from the right
18            onesEncountered++;
19          
20            // Calculate steps needed to move this '1' to its target position
21            // Target position is (stringLength - onesEncountered)
22            // Current position is index
23            // Steps needed = target position - current position
24            totalSteps += stringLength - index - onesEncountered;
25        }
26    }
27  
28    return totalSteps;
29}
30

Time and Space Complexity

The time complexity is O(n), where n is the length of the string. The algorithm iterates through the string once from right to left (from index n-1 to 0), performing constant-time operations at each position. Since we visit each character exactly once, the total time complexity is linear with respect to the input size.

The space complexity is O(1). The algorithm only uses a fixed amount of extra space regardless of the input size. The variables n, ans, cnt, and i are all scalar values that don't grow with the input. No additional data structures like arrays, lists, or recursion stacks are used, resulting in constant space complexity.

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

Common Pitfalls

1. Incorrect Direction of Traversal

A common mistake is traversing from left to right instead of right to left. When traversing left to right, tracking where each '0' should go becomes more complex because you need to know how many '0's are ahead of the current position.

Wrong approach:

def minimumSteps(self, s: str) -> int:
    n = len(s)
    total_steps = 0
    zeros_count = 0
  
    # Wrong: traversing left to right
    for i in range(n):
        if s[i] == '0':
            zeros_count += 1
            # This calculation becomes complicated
            total_steps += i - (zeros_count - 1)
  
    return total_steps

Solution: Always traverse from right to left when counting moves for '1's to reach their final positions on the right side.

2. Off-by-One Error in Position Calculation

Another frequent error is miscalculating the target position or the distance a '1' needs to move.

Wrong calculation:

# Incorrect: forgetting to account for other '1's already counted
steps_for_this_one = (string_length - 1) - current_index

# Or incorrect: using wrong formula
steps_for_this_one = string_length - current_index - ones_count + 1

Solution: The correct formula is (string_length - ones_count) - current_index. Remember that:

  • Target position for the k-th '1' from the right is n - k
  • Current position is current_index
  • Distance = Target - Current = (n - k) - current_index

3. Counting '0's Instead of '1's

Some may try to count '0's and calculate their movements to the left, which can lead to confusion about indexing and position calculations.

Wrong approach:

def minimumSteps(self, s: str) -> int:
    n = len(s)
    total_steps = 0
    zeros_seen = 0
  
    for i in range(n):
        if s[i] == '0':
            # Trying to calculate how far left this '0' needs to go
            total_steps += i - zeros_seen
            zeros_seen += 1
  
    return total_steps

Solution: Stick to counting '1's from right to left. This approach is more intuitive because:

  • We know exactly where each '1' should end up (rightmost positions)
  • The calculation is straightforward: each '1' moves to position n - count_of_ones_seen

4. Not Understanding the Problem Correctly

Some might think each ball needs to be moved individually to its final position, not realizing that adjacent swaps can be optimized.

Solution: Understand that when we count the distance each '1' needs to move, we're actually counting the number of '0's it needs to "jump over" to reach its final position. This automatically accounts for the minimum number of swaps needed.

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

What's the relationship between a tree and a graph?


Recommended Readings

Want a Structured Path to Master System Design Too? Donโ€™t Miss This!

Load More