696. Count Binary Substrings
Problem Description
Given a binary string consisting of only '0's and '1's, you need to count how many substrings satisfy two conditions:
- The substring has an equal number of 0's and 1's
- All 0's are grouped together consecutively, and all 1's are grouped together consecutively
For example, in the string "00110011":
- "0011" is valid (2 zeros followed by 2 ones)
- "01" is valid (1 zero followed by 1 one)
- "1100" is valid (2 ones followed by 2 zeros)
- "0110" is NOT valid (the digits alternate instead of being grouped)
The solution works by first grouping consecutive identical characters. For the string "00110011", this creates groups: [2, 2, 2, 2] representing 2 zeros, 2 ones, 2 zeros, 2 ones.
Then, for each pair of adjacent groups, the number of valid substrings that can be formed between them equals the minimum of the two group sizes. This is because:
- If we have 3 consecutive 0's followed by 2 consecutive 1's ("00011"), we can form:
- "01" (using the last 0 and first 1)
- "0011" (using the last 2 zeros and both 1's)
- Total: min(3, 2) = 2 valid substrings
The algorithm counts all such valid substrings by summing up the minimum values between each pair of adjacent groups.
Intuition
The key insight is recognizing that valid substrings must have a specific pattern: consecutive 0's followed by consecutive 1's (or vice versa). This means we can't have alternating digits like "0101" as a valid substring.
When we look at any valid substring, it must span exactly two groups of consecutive identical characters. For instance, if we see "000111", the valid substrings are formed at the boundary between the 0's and 1's.
The critical observation is that for any two adjacent groups, the number of valid substrings we can form is limited by the smaller group. Why? Because to maintain equal counts of 0's and 1's, we need to match characters from both groups:
- With groups of size 3 and 2, we can only match at most 2 characters from each group
- We can take 1 from each group ("01"), or 2 from each group ("0011")
This leads us to transform the problem: instead of examining every possible substring, we can:
- Compress the string into counts of consecutive characters
- For each adjacent pair of counts, the number of valid substrings equals
min(count1, count2)
For example, "00110011" becomes [2,2,2,2], and between each adjacent pair we get min(2,2) = 2 valid substrings. The total is 2+2+2 = 6.
This approach is efficient because it reduces the problem from checking all possible substrings to simply counting consecutive groups and comparing adjacent pairs.
Learn more about Two Pointers patterns.
Solution Approach
The implementation follows a two-phase approach:
Phase 1: Group Consecutive Characters
We iterate through the string and count consecutive identical characters:
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
This loop:
- Starts at each new character group
- Counts how many consecutive identical characters follow
- Stores each count in list
t
- Moves to the next different character
For example, "00110011" produces t = [2, 2, 2, 2]
.
Phase 2: Calculate Valid Substrings
We then iterate through adjacent pairs in the groups list:
ans = 0
for i in range(1, len(t)):
ans += min(t[i - 1], t[i])
return ans
For each adjacent pair (t[i-1], t[i])
:
- These represent two consecutive groups of different characters (0's and 1's)
- The number of valid substrings between them is
min(t[i-1], t[i])
- We accumulate this count in
ans
Time Complexity: O(n)
where n is the length of the string - we traverse the string once to create groups and then traverse the groups once.
Space Complexity: O(n)
in the worst case where characters alternate (creating n groups), but typically much less as consecutive characters are compressed into single counts.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with the string s = "00110011"
.
Step 1: Group Consecutive Characters
Starting from index 0:
- Position 0-1: We see "00" (2 consecutive 0's) β Add 2 to groups
- Position 2-3: We see "11" (2 consecutive 1's) β Add 2 to groups
- Position 4-5: We see "00" (2 consecutive 0's) β Add 2 to groups
- Position 6-7: We see "11" (2 consecutive 1's) β Add 2 to groups
Groups array: [2, 2, 2, 2]
Step 2: Count Valid Substrings Between Adjacent Groups
Now we examine each pair of adjacent groups:
-
Groups[0] and Groups[1]:
[2, 2]
representing "00" and "11"- min(2, 2) = 2 valid substrings
- These are: "01" (taking 1 from each) and "0011" (taking 2 from each)
-
Groups[1] and Groups[2]:
[2, 2]
representing "11" and "00"- min(2, 2) = 2 valid substrings
- These are: "10" (taking 1 from each) and "1100" (taking 2 from each)
-
Groups[2] and Groups[3]:
[2, 2]
representing "00" and "11"- min(2, 2) = 2 valid substrings
- These are: "01" (taking 1 from each) and "0011" (taking 2 from each)
Total count: 2 + 2 + 2 = 6 valid substrings
The complete list of valid substrings in "00110011":
- "01" at position [1:3]
- "0011" at position [0:4]
- "10" at position [2:4]
- "1100" at position [2:6]
- "01" at position [5:7]
- "0011" at position [4:8]
Each substring has equal 0's and 1's with all identical digits grouped consecutively.
Solution Implementation
1class Solution:
2 def countBinarySubstrings(self, s: str) -> int:
3 """
4 Count binary substrings with equal number of consecutive 0s and 1s.
5
6 Args:
7 s: Binary string containing only '0' and '1'
8
9 Returns:
10 Number of valid binary substrings
11 """
12 index = 0
13 length = len(s)
14 # Store counts of consecutive same characters
15 group_counts = []
16
17 # Group consecutive same characters and count them
18 while index < length:
19 current_count = 1
20
21 # Count consecutive same characters
22 while index + 1 < length and s[index + 1] == s[index]:
23 current_count += 1
24 index += 1
25
26 # Add the count to our list
27 group_counts.append(current_count)
28 index += 1
29
30 # Calculate result by taking minimum of adjacent groups
31 result = 0
32 for i in range(1, len(group_counts)):
33 # For adjacent groups of 0s and 1s, we can form min(count1, count2) valid substrings
34 result += min(group_counts[i - 1], group_counts[i])
35
36 return result
37
1class Solution {
2 public int countBinarySubstrings(String s) {
3 // Initialize variables
4 int index = 0;
5 int length = s.length();
6
7 // List to store counts of consecutive groups of same characters
8 List<Integer> groupCounts = new ArrayList<>();
9
10 // Count consecutive groups of 0s and 1s
11 while (index < length) {
12 int count = 1;
13
14 // Count consecutive same characters
15 while (index + 1 < length && s.charAt(index + 1) == s.charAt(index)) {
16 index++;
17 count++;
18 }
19
20 // Add the count of current group to list
21 groupCounts.add(count);
22 index++;
23 }
24
25 // Calculate result by taking minimum of adjacent group counts
26 int result = 0;
27
28 // For each pair of adjacent groups, add the minimum count
29 // This represents the maximum valid substrings between those groups
30 for (int i = 1; i < groupCounts.size(); i++) {
31 result += Math.min(groupCounts.get(i - 1), groupCounts.get(i));
32 }
33
34 return result;
35 }
36}
37
1class Solution {
2public:
3 int countBinarySubstrings(string s) {
4 int currentIndex = 0;
5 int stringLength = s.size();
6 vector<int> groupSizes; // Store the size of consecutive groups of same characters
7
8 // Count consecutive groups of 0s and 1s
9 while (currentIndex < stringLength) {
10 int groupCount = 1; // Count of current group
11
12 // Count consecutive same characters
13 while (currentIndex + 1 < stringLength && s[currentIndex + 1] == s[currentIndex]) {
14 ++groupCount;
15 ++currentIndex;
16 }
17
18 // Store the group size
19 groupSizes.push_back(groupCount);
20 ++currentIndex;
21 }
22
23 int result = 0;
24
25 // For each adjacent pair of groups, we can form min(group1, group2) valid substrings
26 // Example: "0011" has groups [2,2], we can form min(2,2) = 2 substrings: "01" and "0011"
27 for (int i = 1; i < groupSizes.size(); ++i) {
28 result += min(groupSizes[i - 1], groupSizes[i]);
29 }
30
31 return result;
32 }
33};
34
1function countBinarySubstrings(s: string): number {
2 let currentIndex = 0;
3 const stringLength = s.length;
4 const groupSizes: number[] = []; // Store the size of consecutive groups of same characters
5
6 // Count consecutive groups of 0s and 1s
7 while (currentIndex < stringLength) {
8 let groupCount = 1; // Count of current group
9
10 // Count consecutive same characters
11 while (currentIndex + 1 < stringLength && s[currentIndex + 1] === s[currentIndex]) {
12 groupCount++;
13 currentIndex++;
14 }
15
16 // Store the group size
17 groupSizes.push(groupCount);
18 currentIndex++;
19 }
20
21 let result = 0;
22
23 // For each adjacent pair of groups, we can form min(group1, group2) valid substrings
24 // Example: "0011" has groups [2,2], we can form min(2,2) = 2 substrings: "01" and "0011"
25 for (let i = 1; i < groupSizes.length; i++) {
26 result += Math.min(groupSizes[i - 1], groupSizes[i]);
27 }
28
29 return result;
30}
31
Time and Space Complexity
Time Complexity: O(n)
where n
is the length of the input string s
.
The algorithm consists of two main phases:
- The first while loop traverses the entire string once to count consecutive groups of 0s and 1s. Each character is visited exactly once, resulting in
O(n)
time. - The second for loop iterates through the array
t
which has at mostn
elements (in the worst case when characters alternate), takingO(n)
time.
Therefore, the overall time complexity is O(n) + O(n) = O(n)
.
Space Complexity: O(n)
in the worst case.
The additional space is used by the array t
which stores the counts of consecutive groups. In the worst case scenario where the string alternates between 0 and 1 (e.g., "010101..."), we would have n
groups, each of size 1. This would result in an array t
of size n
, giving us O(n)
space complexity. In the best case where the string consists of all same characters, t
would only have 1 element, but we consider worst-case for complexity analysis.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Attempting to Check All Substrings Directly
A common mistake is trying to validate every possible substring by checking if it has equal 0's and 1's and if they're grouped consecutively:
# Inefficient approach - DON'T DO THIS
def countBinarySubstrings(s):
count = 0
n = len(s)
for i in range(n):
for j in range(i+1, n+1):
substring = s[i:j]
if isValid(substring): # Check equal 0s/1s and consecutive grouping
count += 1
return count
Why it's problematic: This has O(nΒ³) time complexity - O(nΒ²) for generating substrings and O(n) for validation.
Solution: Use the grouping approach that recognizes valid substrings only form between adjacent groups of different characters.
2. Misunderstanding Valid Substring Formation
Developers often think valid substrings can span multiple groups:
# Incorrect thinking: "001100" might seem valid (equal 0s and 1s) # But it's actually formed from 3 groups: [2 zeros, 2 ones, 2 zeros]
Why it's wrong: Valid substrings must have exactly one transition point between 0's and 1's. They can only form between two adjacent groups.
Solution: Remember that valid substrings have the pattern "000...111..." or "111...000..." with exactly one transition.
3. Off-by-One Error in Group Counting
A subtle bug can occur when counting consecutive characters:
# Buggy version while i < n: cnt = 1 while i < n - 1 and s[i] == s[i + 1]: # Wrong comparison! cnt += 1 i += 1 t.append(cnt) i += 1
Why it's problematic: Comparing s[i]
with s[i+1]
instead of s[i+1]
with s[i]
can lead to incorrect indexing.
Solution: Use the correct comparison s[i+1] == s[i]
and ensure i+1 < n
for bounds checking.
4. Space Optimization Oversight
While the provided solution is correct, it can be optimized to use O(1) space:
def countBinarySubstrings(self, s: str) -> int:
prev_group = 0
curr_group = 1
result = 0
for i in range(1, len(s)):
if s[i] == s[i-1]:
curr_group += 1
else:
result += min(prev_group, curr_group)
prev_group = curr_group
curr_group = 1
result += min(prev_group, curr_group) # Don't forget the last pair!
return result
Why it's better: Instead of storing all groups, we only track the current and previous group sizes, reducing space complexity from O(n) to O(1).
5. Forgetting Edge Cases
Common edge cases that are missed:
- Empty string or single character string
- String with all same characters (e.g., "0000" or "1111")
- Alternating characters (e.g., "010101")
Solution: The grouping approach naturally handles these cases, but ensure your implementation doesn't assume at least two different groups exist.
Which data structure is used to implement priority queue?
Recommended Readings
Tech Interview Pattern Two Pointers Introduction If you prefer videos here's a super quick introduction to Two Pointers div class responsive iframe iframe src https www youtube com embed xZ4AfXHQ1VQ title YouTube video player frameborder 0 allow accelerometer autoplay clipboard write encrypted media gyroscope picture in picture allowfullscreen iframe div Two pointers is a common interview
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donβt Miss This!