Count Binary Substrings
EasyPrompt
Given a binary string s, return the number of non-empty substrings that have the same number of 0's and 1's, and all the 0's and all the 1's in these substrings are grouped consecutively.
Substrings that occur multiple times are counted the number of times they occur.
Example 1:
Input: s = "00110011"
Output: 6
Explanation: There are 6 substrings that have equal number of consecutive 1's and 0's: "0011", "01", "1100", "10", "0011", and "01".
Notice that some of these substrings repeat and are counted the number of times they occur.
Also, "00110011" is not a valid substring because all the 0's (and 1's) are not grouped together.Example 2:
Input: s = "10101"
Output: 4
Explanation: There are 4 substrings: "10", "01", "10", "01" that have equal number of consecutive 1's and 0's.
Constraints:
1 <= s.length <= 105s[i]is either'0'or'1'.
Approaches
3 approaches with complexity analysis and trade-offs.
This approach iterates through the string to find all possible "centers" of a valid substring. A center is a point where the character changes, i.e., a "01" or "10" pattern. For each center found, we expand outwards in both directions, checking if the characters match the initial pattern (e.g., '0's to the left, '1's to the right). We count how many valid substrings can be formed by this expansion.
Algorithm
- Initialize a counter
result = 0. - Loop through the string
sfromi = 0tos.length() - 2. - Check if
s.charAt(i) != s.charAt(i+1). If they are different, we've found a center.- Increment
resultbecause we found a valid substring of length 2 (e.g., "01" or "10"). - Initialize
left = i - 1andright = i + 2. - Start a while loop to expand outwards:
while (left >= 0 && right < s.length()).- Inside the loop, check if
s.charAt(left) == s.charAt(i)ands.charAt(right) == s.charAt(i+1). - If the characters match the pattern, we've found another valid substring. Increment
result, and move the pointers:left--,right++. - If they don't match, the expansion for this center is over. Break the inner while loop.
- Inside the loop, check if
- Increment
- After the main loop finishes, return
result.
Walkthrough
The core idea is that any valid substring must contain a "01" or "10" boundary. We can iterate through the string with an index i from 0 to n-2. If s[i] is different from s[i+1], we have found a potential center. For example, if we find s[i] = '0' and s[i+1] = '1', we have found the valid substring "01". This is our base case. We then try to expand this substring. We use two pointers, left = i - 1 and right = i + 2. We move left to the left and right to the right as long as s[left] is '0' and s[right] is '1'. For each successful expansion, we find another valid substring (e.g., "0011", "000111", etc.) and increment our count. We repeat this process for all possible centers in the string.
class Solution { public int countBinarySubstrings(String s) { int ans = 0; for (int i = 0; i < s.length() - 1; i++) { if (s.charAt(i) != s.charAt(i + 1)) { ans++; // For the base case like "01" or "10" int left = i - 1; int right = i + 2; while (left >= 0 && right < s.length() && s.charAt(left) == s.charAt(i) && s.charAt(right) == s.charAt(i + 1)) { ans++; left--; right++; } } } return ans; }}Complexity
Time
O(N^2), where N is the length of the string. In the worst case, for each of the N potential centers, the expansion could take up to O(N) time.
Space
O(1), as we only use a few variables for pointers and the count.
Trade-offs
Pros
Simple to understand and implement.
It correctly identifies all valid substrings by focusing on their central property.
Uses constant extra space.
Cons
The time complexity is quadratic, which is too slow for the given constraints (N up to 10^5) and will result in a 'Time Limit Exceeded' error on most platforms.
A more efficient approach is to first process the string to find the lengths of consecutive blocks of identical characters. For example, "001110" would be represented as [2, 3, 1]. A valid substring is formed at the boundary of any two adjacent groups. The number of valid substrings at each boundary is the minimum of the lengths of the two adjacent groups.
Algorithm
- Create a list of integers,
groups. - If the input string
sis empty, return 0. - Initialize a counter
count = 1. - Iterate through the string from the second character (
i = 1tos.length() - 1):- If
s.charAt(i) == s.charAt(i-1), incrementcount. - Otherwise, the group has ended. Add the current
countto thegroupslist and resetcount = 1.
- If
- After the loop, add the last
countto thegroupslist. - Initialize
result = 0. - Iterate through the
groupslist fromi = 0togroups.size() - 2:- Add
Math.min(groups.get(i), groups.get(i+1))toresult.
- Add
- Return
result.
Walkthrough
The key observation is that any valid substring is composed of two adjacent, consecutive blocks of '0's and '1's of equal length. For instance, "0011" is formed by a block of two '0's and a block of two '1's. We can first iterate through the input string s and count the lengths of consecutive identical characters. We store these lengths in a list or array. For s = "00110011", the list of group lengths would be [2, 2, 2, 2]. Once we have this list of group lengths, we can iterate through it. For any two adjacent lengths, groups[i] and groups[i+1], they represent two adjacent blocks of different characters. The number of valid substrings they can form is min(groups[i], groups[i+1]). For example, with groups of length 2 and 3 ("00111"), we can form "01" and "0011". The number is min(2, 3) = 2. We sum up these minimums for all adjacent pairs in the group lengths list to get the total count.
import java.util.ArrayList;import java.util.List; class Solution { public int countBinarySubstrings(String s) { List<Integer> groups = new ArrayList<>(); if (s == null || s.length() == 0) { return 0; } int count = 1; for (int i = 1; i < s.length(); i++) { if (s.charAt(i) == s.charAt(i - 1)) { count++; } else { groups.add(count); count = 1; } } groups.add(count); int ans = 0; for (int i = 0; i < groups.size() - 1; i++) { ans += Math.min(groups.get(i), groups.get(i + 1)); } return ans; }}Complexity
Time
O(N), where N is the length of the string. We perform two separate linear passes: one to create the `groups` list and another to sum the results.
Space
O(N) in the worst case. The `groups` list can contain up to N elements if the string alternates characters at every position (e.g., "010101...").
Trade-offs
Pros
Much faster than the brute-force approach, with a linear time complexity that passes the constraints.
The logic is straightforward: group, then count.
Cons
Requires extra space to store the group lengths, which can be proportional to the input size in the worst case.
This is the most optimal approach. It builds upon the grouping idea but eliminates the need for an auxiliary array to store group lengths. By iterating through the string once, we can compute the group lengths on the fly and maintain only the lengths of the current and previous groups.
Algorithm
- Initialize
result = 0,prevGroupLength = 0, andcurrGroupLength = 1. - Iterate through the string
sfromi = 1tos.length() - 1.- If
s.charAt(i) == s.charAt(i-1), it's part of the same group, so incrementcurrGroupLength. - If
s.charAt(i) != s.charAt(i-1), a group has just ended.- Add
Math.min(prevGroupLength, currGroupLength)toresult. - The current group now becomes the previous group for the next boundary, so set
prevGroupLength = currGroupLength. - Reset
currGroupLength = 1for the new group starting ati.
- Add
- If
- After the loop, there's one last group to account for. Add
Math.min(prevGroupLength, currGroupLength)toresultone more time. - Return
result.
Walkthrough
We can observe from the previous approach that to calculate the result, we only need the lengths of two adjacent groups at any given time. This means we don't need to store all group lengths. We can use two variables, prevGroupLength and currGroupLength, to keep track of the lengths of the previous and current consecutive blocks of characters. We iterate through the string. As we encounter a new group of characters (i.e., when s[i] != s[i-1]), the currGroupLength of the just-finished group is now known. We can then calculate the number of valid substrings formed by this group and the prevGroupLength by taking their minimum. After processing the boundary, the currGroupLength becomes the new prevGroupLength for the next iteration, and we reset currGroupLength to 1 for the new group we've just started.
class Solution { public int countBinarySubstrings(String s) { int ans = 0; int prevGroupLength = 0; int currGroupLength = 1; for (int i = 1; i < s.length(); i++) { if (s.charAt(i) == s.charAt(i - 1)) { currGroupLength++; } else { ans += Math.min(prevGroupLength, currGroupLength); prevGroupLength = currGroupLength; currGroupLength = 1; } } // Add the last comparison ans += Math.min(prevGroupLength, currGroupLength); return ans; }}Complexity
Time
O(N), where N is the length of the string. We iterate through the string only once.
Space
O(1). We only use a few constant-size variables to store the counts.
Trade-offs
Pros
Highly efficient in both time and space.
It's the optimal solution for this problem, passing all constraints with ease.
Cons
The logic might be slightly less intuitive at first glance compared to the approach that explicitly stores group lengths.
Solutions
Solution
class Solution {public int countBinarySubstrings(String s) { int i = 0, n = s.length(); List<Integer> t = new ArrayList<>(); while (i < n) { int cnt = 1; while (i + 1 < n && s.charAt(i + 1) == s.charAt(i)) { ++i; ++cnt; } t.add(cnt); ++i; } int ans = 0; for (i = 1; i < t.size(); ++i) { ans += Math.min(t.get(i - 1), t.get(i)); } return ans; }}class Solution { public: int countBinarySubstrings ( string s ) { int i = 0 , n = s . size (); vector < int > t ; while ( i < n ) { int cnt = 1 ; while ( i + 1 < n && s [ i + 1 ] == s [ i ]) { ++ cnt ; ++ i ; } t . push_back ( cnt ); ++ i ; } int ans = 0 ; for ( i = 1 ; i < t . size (); ++ i ) ans += min ( t [ i - 1 ], t [ i ]); return ans ; } };class Solution : def countBinarySubstrings ( self , s : str ) -> int : i , n = 0 , len ( s ) t = [] while i < n : cnt = 1 while i + 1 < n and s [ i + 1 ] == s [ i ]: cnt += 1 i += 1 t . append ( cnt ) i += 1 ans = 0 for i in range ( 1 , len ( t )): ans += min ( t [ i - 1 ], t [ i ]) 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.