Separate Black and White Balls
MedPrompt
There are n balls on a table, each ball has a color black or white.
You are given a 0-indexed binary string s of length n, where 1 and 0 represent black and white balls, respectively.
In each step, you can choose two adjacent balls and swap them.
Return the minimum number of steps to group all the black balls to the right and all the white balls to the left.
Example 1:
Input: s = "101"
Output: 1
Explanation: We can group all the black balls to the right in the following way:
- Swap s[0] and s[1], s = "011".
Initially, 1s are not grouped together, requiring at least 1 step to group them to the right.Example 2:
Input: s = "100"
Output: 2
Explanation: We can group all the black balls to the right in the following way:
- Swap s[0] and s[1], s = "010".
- Swap s[1] and s[2], s = "001".
It can be proven that the minimum number of steps needed is 2.Example 3:
Input: s = "0111"
Output: 0
Explanation: All the black balls are already grouped to the right.
Constraints:
1 <= n == s.length <= 105s[i]is either'0'or'1'.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach directly counts the number of 'inversions' in the string. An inversion is defined as a pair of a black ball ('1') appearing to the left of a white ball ('0'). The minimum number of adjacent swaps required to sort the balls is equal to the total number of such inversions. We can find this by using nested loops to check every possible pair of characters in the string.
Algorithm
- Initialize a variable
swapsto 0. - Use a nested loop structure. The outer loop iterates from the beginning of the string to the end, with index
i. - If the character at
s[i]is '1', start an inner loop with indexjfromi + 1to the end of the string. - Inside the inner loop, if the character
s[j]is '0', it means we have found an inversion pair ('1' before '0'). Increment theswapscounter. - After both loops complete,
swapswill hold the total number of such pairs, which is the minimum number of adjacent swaps required. - Return the final
swapscount.
Walkthrough
The fundamental insight is that to move all '1's to the right of all '0's, every '1' that is currently to the left of a '0' must be swapped past it. Each such pair (s[i] = '1', s[j] = '0') where i < j contributes to the total number of swaps. A brute-force method is to simply iterate through all possible pairs of indices (i, j) with i < j and count how many of them form an inversion.
class Solution { public long minimumSteps(String s) { long swaps = 0; int n = s.length(); // Convert string to char array for potentially faster access, though not strictly necessary. char[] balls = s.toCharArray(); for (int i = 0; i < n; i++) { // If we find a black ball ('1')... if (balls[i] == '1') { // ...we count how many white balls ('0') are to its right. for (int j = i + 1; j < n; j++) { if (balls[j] == '0') { // Each '0' to the right of a '1' represents an inversion. swaps++; } } } } return swaps; }}Complexity
Time
O(N^2), where N is the length of the string `s`. The nested loops cause the algorithm to check approximately N^2/2 pairs in the worst case (e.g., a string of '1's followed by '0's).
Space
O(1), as we only use a constant amount of extra space for loop variables and the counter. If we convert the string to a char array, it would be O(N), but this is not essential for the logic.
Trade-offs
Pros
It's a straightforward implementation of the problem's definition in terms of inversions.
Easy to understand and reason about.
Cons
The quadratic time complexity makes it too slow for the given constraints, leading to a 'Time Limit Exceeded' error on large test cases.
This optimal approach avoids the nested loops by performing a single pass over the string. The key idea is to count inversions more efficiently. As we iterate from left to right, we keep track of the number of black balls ('1's) seen so far. When we encounter a white ball ('0'), we know it needs to be moved to the left of all the black balls we've already passed. The number of swaps required for this specific white ball is exactly the number of black balls currently to its left.
Algorithm
- Initialize a variable
swapsto 0 (as a long to prevent overflow). - Initialize a counter
onesCountto 0. - Iterate through the string from left to right.
- If the current character is a '1', increment
onesCount. - If the current character is a '0', it means this '0' is positioned to the right of
onesCount'1's. Each of these '1's must eventually be moved to the right of this '0'. This contributesonesCountswaps to the total. So, add the currentonesCounttoswaps. - After the loop finishes,
swapsholds the total minimum number of steps. Returnswaps.
Walkthrough
Instead of re-calculating for each '1', we can accumulate the result in one pass. Let's process the string from left to right. We maintain a count of the '1's we have seen so far. When we encounter a '0', we know that this '0' is in the wrong place relative to all the '1's we've already counted. To move this '0' to its correct sorted position (to the left of all '1's), it must be swapped past every one of the '1's we've seen. Therefore, we add the current count of '1's to our total swaps.
For example, in s = "1010":
i=0, s[0]='1': We've seen one '1'.onesCount = 1.i=1, s[1]='0': We see a '0'. It must be swapped past the one '1' we've seen.swaps += 1. Totalswaps = 1.i=2, s[2]='1': We've now seen two '1's in total.onesCount = 2.i=3, s[3]='0': We see another '0'. It must be swapped past the two '1's we've seen.swaps += 2. Totalswaps = 1 + 2 = 3.
The final answer is 3.
class Solution { public long minimumSteps(String s) { long swaps = 0; int onesCount = 0; for (char c : s.toCharArray()) { if (c == '1') { // Found a black ball, just increment the count. onesCount++; } else { // c == '0' // Found a white ball. It needs to be moved past all the black balls // we have encountered so far. The number of swaps needed for this // white ball is equal to the current count of black balls. swaps += onesCount; } } return swaps; }}Complexity
Time
O(N), where N is the length of the string `s`. We only iterate through the string once.
Space
O(1), as we only use a few variables to store the counts, regardless of the input size.
Trade-offs
Pros
Extremely efficient with a linear time complexity.
Uses constant extra space.
Simple to implement with a single loop.
Cons
The logic, while simple, might be slightly less intuitive than the direct brute-force counting of inversion pairs.
Solutions
Solution
class Solution {public long minimumSteps(String s) { long ans = 0; int cnt = 0; int n = s.length(); for (int i = n - 1; i >= 0; --i) { if (s.charAt(i) == '1') { ++cnt; ans += n - i - cnt; } } return ans; }}class Solution {public: long long minimumSteps(string s) { long long ans = 0; int cnt = 0; int n = s.size(); for (int i = n - 1; i >= 0; --i) { if (s[i] == '1') { ++cnt; ans += n - i - cnt; } } return ans; }};class Solution: def minimumSteps(self, s: str) -> int: n = len(s) ans = cnt = 0 for i in range(n - 1, - 1, - 1): if s[i] == '1': cnt += 1 ans += n - i - cnt return ansVideo walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.