2533. Number of Good Binary Strings 🔒
Problem Description
You need to count how many "good" binary strings exist within certain constraints.
A binary string consists only of 0s and 1s. When we look at these strings, consecutive identical digits form "blocks". For example, in the string 0011101111100
, we have blocks of zeros [2, 1, 2]
(at the beginning, middle, and end) and blocks of ones [3, 5]
.
A binary string is considered good if it meets three conditions:
-
Length constraint: The string's length must be between
minLength
andmaxLength
(inclusive). -
Ones constraint: Every block of consecutive 1s must have a size that's a multiple of
oneGroup
. For instance, ifoneGroup = 3
, then blocks of 1s can only be of size 3, 6, 9, etc. -
Zeros constraint: Every block of consecutive 0s must have a size that's a multiple of
zeroGroup
. For instance, ifzeroGroup = 2
, then blocks of 0s can only be of size 2, 4, 6, etc.
Important note: The value 0 is considered a multiple of every number. This means an empty block (no consecutive 0s or 1s at all) is valid.
Your task is to find the total number of good binary strings. Since this number can be very large, return the result modulo 10^9 + 7
.
For example, if minLength = 2
, maxLength = 3
, oneGroup = 1
, and zeroGroup = 2
:
- The string
00
is good (one block of 2 zeros, which is a multiple of 2) - The string
11
is good (one block of 2 ones, which is a multiple of 1) - The string
001
is good (one block of 2 zeros and one block of 1 one) - And so on...
You need to count all such valid strings and return the count.
Intuition
Think about how we build a good binary string step by step. At any point, we can either add a complete block of consecutive 1s or a complete block of consecutive 0s. Since blocks must be multiples of oneGroup
or zeroGroup
, we can only add blocks of size oneGroup
, 2*oneGroup
, 3*oneGroup
... for 1s, and similarly for 0s.
But here's the key insight: instead of thinking about adding variable-sized blocks, we can simplify by only considering adding blocks of the minimum valid size - exactly oneGroup
1s or exactly zeroGroup
0s at a time. Why? Because any larger valid block (like 2*oneGroup
) can be formed by adding the minimum block multiple times consecutively.
This transforms our problem into something similar to the "climbing stairs" problem. If we want to form a string of length i
, we can either:
- Take a valid string of length
i - oneGroup
and add a block ofoneGroup
ones - Take a valid string of length
i - zeroGroup
and add a block ofzeroGroup
zeros
This leads us to dynamic programming. Let f[i]
represent the number of ways to form a valid string of exactly length i
. Then:
f[0] = 1
(empty string is valid)- For any
i > 0
:f[i] = f[i - oneGroup] + f[i - zeroGroup]
(if the indices are valid)
The beauty of this approach is that it automatically handles all valid combinations. When we add a block to an existing string, we don't need to worry about whether we're extending an existing block or starting a new one - the math works out correctly either way because we're always adding complete valid blocks.
Finally, since we want strings of length between minLength
and maxLength
, we sum up all f[i]
where minLength ≤ i ≤ maxLength
.
Learn more about Dynamic Programming patterns.
Solution Approach
We implement the dynamic programming solution using a 1D array f
where f[i]
represents the number of valid binary strings of length i
.
Initialization:
- Create an array
f
of sizemaxLength + 1
- Set
f[0] = 1
(empty string is valid) - All other positions start at 0
State Transition:
For each position i
from 1 to maxLength
, we calculate f[i]
based on two possible transitions:
-
Adding a block of ones: If
i >= oneGroup
, we can form a string of lengthi
by taking any valid string of lengthi - oneGroup
and appendingoneGroup
ones to it. This contributesf[i - oneGroup]
ways. -
Adding a block of zeros: If
i >= zeroGroup
, we can form a string of lengthi
by taking any valid string of lengthi - zeroGroup
and appendingzeroGroup
zeros to it. This contributesf[i - zeroGroup]
ways.
The recurrence relation is:
f[i] = f[i - oneGroup] + f[i - zeroGroup]
(where we only add each term if the index is valid)
Implementation Details:
f = [1] + [0] * maxLength # f[0] = 1, rest are 0
for i in range(1, len(f)):
if i - oneGroup >= 0:
f[i] += f[i - oneGroup]
if i - zeroGroup >= 0:
f[i] += f[i - zeroGroup]
f[i] %= mod # Apply modulo to prevent overflow
Final Answer:
Sum all values from f[minLength]
to f[maxLength]
(inclusive). This gives us the total count of valid strings with lengths in the required range.
return sum(f[minLength:]) % mod
The time complexity is O(maxLength)
as we iterate through each position once. The space complexity is also O(maxLength)
for storing the DP array.
Ready to land your dream job?
Unlock your dream job with a 5-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through a small example with minLength = 2
, maxLength = 4
, oneGroup = 2
, and zeroGroup = 1
.
We need to find all valid binary strings of length 2, 3, or 4 where:
- Every block of 1s has size that's a multiple of 2 (so blocks of size 2, 4, 6...)
- Every block of 0s has size that's a multiple of 1 (so blocks of size 1, 2, 3...)
Step 1: Initialize DP array
Create array f
where f[i]
= number of valid strings of length i
:
f[0] = 1
(empty string is valid)f[1] = 0, f[2] = 0, f[3] = 0, f[4] = 0
(initially)
Step 2: Build up the DP table
For i = 1
:
- Can we add a block of 2 ones? No, because
1 - 2 = -1
(invalid index) - Can we add a block of 1 zero? Yes, from
f[0]
:f[1] = f[0] = 1
- Valid strings of length 1: "0"
For i = 2
:
- Can we add a block of 2 ones? Yes, from
f[0]
: contributesf[0] = 1
- Can we add a block of 1 zero? Yes, from
f[1]
: contributesf[1] = 1
f[2] = 1 + 1 = 2
- Valid strings of length 2: "11" (from f[0] + two 1s), "00" (from f[1] + one 0)
For i = 3
:
- Can we add a block of 2 ones? Yes, from
f[1]
: contributesf[1] = 1
- Can we add a block of 1 zero? Yes, from
f[2]
: contributesf[2] = 2
f[3] = 1 + 2 = 3
- Valid strings of length 3: "011" (from "0" + two 1s), "110" (from "11" + one 0), "000" (from "00" + one 0)
For i = 4
:
- Can we add a block of 2 ones? Yes, from
f[2]
: contributesf[2] = 2
- Can we add a block of 1 zero? Yes, from
f[3]
: contributesf[3] = 3
f[4] = 2 + 3 = 5
- Valid strings of length 4: "1111" (from "11" + two 1s), "0011" (from "00" + two 1s), "1100" (from "110" + one 0), "0110" (from "011" + one 0), "0000" (from "000" + one 0)
Step 3: Calculate final answer Sum values for lengths in range [2, 4]:
f[2] + f[3] + f[4] = 2 + 3 + 5 = 10
The answer is 10 valid binary strings.
Solution Implementation
1class Solution:
2 def goodBinaryStrings(
3 self, minLength: int, maxLength: int, oneGroup: int, zeroGroup: int
4 ) -> int:
5 # Modulo value for large number handling
6 MOD = 10**9 + 7
7
8 # Dynamic programming array where dp[i] represents the number of ways
9 # to form a valid binary string of length i
10 # dp[0] = 1 represents an empty string (base case)
11 dp = [1] + [0] * maxLength
12
13 # Build up the dp array for all lengths from 1 to maxLength
14 for length in range(1, len(dp)):
15 # If we can append a group of ones (oneGroup consecutive 1s)
16 # to a string of length (length - oneGroup), add those possibilities
17 if length - oneGroup >= 0:
18 dp[length] += dp[length - oneGroup]
19
20 # If we can append a group of zeros (zeroGroup consecutive 0s)
21 # to a string of length (length - zeroGroup), add those possibilities
22 if length - zeroGroup >= 0:
23 dp[length] += dp[length - zeroGroup]
24
25 # Apply modulo to prevent integer overflow
26 dp[length] %= MOD
27
28 # Sum up all valid strings with lengths between minLength and maxLength (inclusive)
29 # dp[minLength:] gives us all elements from index minLength to the end
30 result = sum(dp[minLength:]) % MOD
31
32 return result
33
1class Solution {
2 public int goodBinaryStrings(int minLength, int maxLength, int oneGroup, int zeroGroup) {
3 // Modulo value for preventing integer overflow
4 final int MOD = 1_000_000_007;
5
6 // Dynamic programming array where dp[i] represents the number of valid strings of length i
7 int[] dp = new int[maxLength + 1];
8
9 // Base case: empty string (length 0) counts as one valid way
10 dp[0] = 1;
11
12 // Build up the DP table for all lengths from 1 to maxLength
13 for (int currentLength = 1; currentLength <= maxLength; ++currentLength) {
14 // If we can append a group of ones (oneGroup length) to form current length
15 if (currentLength - oneGroup >= 0) {
16 dp[currentLength] = (dp[currentLength] + dp[currentLength - oneGroup]) % MOD;
17 }
18
19 // If we can append a group of zeros (zeroGroup length) to form current length
20 if (currentLength - zeroGroup >= 0) {
21 dp[currentLength] = (dp[currentLength] + dp[currentLength - zeroGroup]) % MOD;
22 }
23 }
24
25 // Sum up all valid strings with lengths between minLength and maxLength
26 int totalCount = 0;
27 for (int length = minLength; length <= maxLength; ++length) {
28 totalCount = (totalCount + dp[length]) % MOD;
29 }
30
31 return totalCount;
32 }
33}
34
1class Solution {
2public:
3 int goodBinaryStrings(int minLength, int maxLength, int oneGroup, int zeroGroup) {
4 // Modulo value for preventing integer overflow
5 const int MOD = 1000000007;
6
7 // dp[i] represents the number of ways to form a good binary string of length i
8 // We need array size of maxLength + 1 to include length from 0 to maxLength
9 int dp[maxLength + 1];
10 memset(dp, 0, sizeof(dp));
11
12 // Base case: empty string has one way to be formed
13 dp[0] = 1;
14
15 // Build up the DP table for all lengths from 1 to maxLength
16 for (int currentLength = 1; currentLength <= maxLength; ++currentLength) {
17 // If we can append a group of ones to reach current length
18 if (currentLength - oneGroup >= 0) {
19 dp[currentLength] = (dp[currentLength] + dp[currentLength - oneGroup]) % MOD;
20 }
21
22 // If we can append a group of zeros to reach current length
23 if (currentLength - zeroGroup >= 0) {
24 dp[currentLength] = (dp[currentLength] + dp[currentLength - zeroGroup]) % MOD;
25 }
26 }
27
28 // Sum up all valid string counts within the range [minLength, maxLength]
29 int totalCount = 0;
30 for (int length = minLength; length <= maxLength; ++length) {
31 totalCount = (totalCount + dp[length]) % MOD;
32 }
33
34 return totalCount;
35 }
36};
37
1/**
2 * Counts the number of good binary strings within a given length range.
3 * A good binary string is one where consecutive 1s appear in groups of size oneGroup
4 * and consecutive 0s appear in groups of size zeroGroup.
5 *
6 * @param minLength - Minimum length of binary strings to consider
7 * @param maxLength - Maximum length of binary strings to consider
8 * @param oneGroup - Required size of consecutive 1s groups
9 * @param zeroGroup - Required size of consecutive 0s groups
10 * @returns The count of good binary strings modulo 10^9 + 7
11 */
12function goodBinaryStrings(
13 minLength: number,
14 maxLength: number,
15 oneGroup: number,
16 zeroGroup: number,
17): number {
18 // Modulo value to prevent integer overflow
19 const MOD: number = 10 ** 9 + 7;
20
21 // Dynamic programming array where dp[i] represents the number of good binary strings of length i
22 const dp: number[] = Array(maxLength + 1).fill(0);
23
24 // Base case: empty string is considered valid
25 dp[0] = 1;
26
27 // Build up the DP table for all lengths from 1 to maxLength
28 for (let length = 1; length <= maxLength; ++length) {
29 // If we can add a group of ones to form current length
30 if (length >= oneGroup) {
31 dp[length] = (dp[length] + dp[length - oneGroup]) % MOD;
32 }
33
34 // If we can add a group of zeros to form current length
35 if (length >= zeroGroup) {
36 dp[length] = (dp[length] + dp[length - zeroGroup]) % MOD;
37 }
38 }
39
40 // Sum up all valid string counts from minLength to maxLength
41 let totalCount: number = 0;
42 for (let i = minLength; i <= maxLength; ++i) {
43 totalCount = (totalCount + dp[i]) % MOD;
44 }
45
46 return totalCount;
47}
48
Time and Space Complexity
The time complexity is O(n)
, where n = maxLength
. The algorithm uses dynamic programming with a single loop that iterates from 1
to maxLength
(inclusive). Within each iteration, it performs constant-time operations: at most two additions, two array accesses, and one modulo operation. Therefore, the overall time complexity is linear with respect to maxLength
.
The space complexity is O(n)
, where n = maxLength
. The algorithm creates an array f
of size maxLength + 1
to store the dynamic programming states. This is the dominant space usage in the algorithm. The additional variables (mod
, i
, and the sum computation) use constant space. Thus, the total space complexity is linear with respect to maxLength
.
Learn more about how to find time and space complexity quickly.
Common Pitfalls
1. Misunderstanding the DP State Transition
Pitfall: A common mistake is thinking that dp[i]
represents strings ending with a specific digit (0 or 1), leading to incorrect state transitions like trying to track the last character or attempting to alternate between 0s and 1s.
Why it happens: The problem mentions "blocks" of consecutive digits, which might mislead you into thinking you need to track what digit the string ends with to ensure valid block formations.
Correct Understanding: The DP state dp[i]
represents ALL valid binary strings of length i
, regardless of what digit they end with. When we add oneGroup
ones or zeroGroup
zeros, we're adding a complete valid block to any valid string, and the block constraint is automatically satisfied because we only add blocks of the required sizes.
2. Incorrect Modulo Application
Pitfall: Forgetting to apply modulo during intermediate calculations, only applying it at the final return:
# WRONG - can cause integer overflow
for length in range(1, len(dp)):
if length - oneGroup >= 0:
dp[length] += dp[length - oneGroup]
if length - zeroGroup >= 0:
dp[length] += dp[length - zeroGroup]
# Missing modulo here!
return sum(dp[minLength:]) % MOD # Only applying modulo at the end
Solution: Apply modulo after each update to prevent overflow:
# CORRECT
for length in range(1, len(dp)):
if length - oneGroup >= 0:
dp[length] += dp[length - oneGroup]
if length - zeroGroup >= 0:
dp[length] += dp[length - zeroGroup]
dp[length] %= MOD # Apply modulo after each calculation
3. Edge Case: Same Group Sizes
Pitfall: Double-counting when oneGroup == zeroGroup
. Some might think special handling is needed to avoid counting the same transitions twice.
Why it's not a problem: Even when oneGroup == zeroGroup
, the transitions represent different actions (adding ones vs adding zeros), creating different binary strings. For example, if both groups are size 2, adding "11" vs "00" to an empty string creates different valid strings.
4. Overthinking Block Combinations
Pitfall: Trying to explicitly enumerate or track different combinations of blocks (like "first add 2 zeros, then 3 ones, then 2 zeros...").
Why it's unnecessary: The DP approach automatically handles all valid combinations. When we compute dp[i]
, it includes ALL possible ways to form valid strings of length i
through any valid sequence of block additions. The beauty of this solution is that it abstracts away the complexity of block ordering and combinations.
Which of the following array represent a max heap?
Recommended Readings
What is Dynamic Programming Prerequisite DFS problems dfs_intro Backtracking problems backtracking Memoization problems memoization_intro Pruning problems backtracking_pruning Dynamic programming is an algorithmic optimization technique that breaks down a complicated problem into smaller overlapping sub problems in a recursive manner and uses solutions to the sub problems to construct a solution
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!