[Medium] 2466. Count Ways To Build Good Strings
Given the integers zero, one, low, and high, we can construct a string by starting with an empty string, and then at each step perform either of the following:
- Append the character
'0'zerotimes. - Append the character
'1'onetimes.
This can be performed any number of times.
A good string is a string constructed by the above process having a length between low and high (inclusive).
Return the number of different good strings you can construct satisfying these properties. Since the answer can be large, return it modulo 10^9 + 7.
Examples
Example 1:
Input: low = 3, high = 3, zero = 1, one = 1
Output: 8
Explanation:
One possible valid good string is "011".
It can be constructed as follows: "" -> "0" -> "01" -> "011".
All binary strings from "000" to "111" are good strings in this example.
Example 2:
Input: low = 2, high = 3, zero = 1, one = 2
Output: 5
Explanation: The good strings are "00", "11", "000", "110", and "011".
Constraints
1 <= low <= high <= 10^51 <= zero, one <= high
Thinking Process
- DP State:
dp[i]= number of ways to build string of lengthi
- Define state: what subproblem does
dp[i](ordp[i][j]) represent? - Recurrence: how does the answer build from smaller indices?
- Base cases first; optimize space if only prior row/layer is needed.
Common Approaches
Typical techniques for this pattern:
| Approach | Time | Space | Notes |
|---|---|---|---|
| 1D DP (this problem) | O(n) | O(n) or O(1) | Linear recurrence |
| 2D DP | O(nm) | O(nm) or O(n) | Grid or two-sequence problems |
| State machine DP | O(n) | O(1) | Buy/sell, hold/not-hold states |
| Memoization (top-down) | Same as DP | O(n) | Recursive + cache |
Solution
Time Complexity: O(high)
Space Complexity: O(high)
Use bottom-up DP to calculate the number of ways to build strings of each length.
class Solution {
List<Integer> dp = new ArrayList<>();
int MOD = 1e9 + 7;
public int countGoodStrings(int low, int high, int zero, int one) {
dp.resize(high + 1, 0);
dp[0] = 1; // Base case: empty String
for(int i = 1; i <= high; i++) {
if(i - zero >) dp[i] = (dp[i] + dp[i - zero]) % MOD;
if(i - one >) dp[i] = (dp[i] + dp[i - one]) % MOD;
}
int rtn = 0;
for(int i = low; i <= high; i++) {
rtn = (rtn + dp[i]) % MOD;
}
return rtn;
}
}
Solution Explanation
Approach: 1D DP (this problem)
Key idea: 1. DP State: dp[i] = number of ways to build string of length i
How the code works:
- DP State:
dp[i]= number of ways to build string of lengthi- Define state: what subproblem does
dp[i](ordp[i][j]) represent? - Recurrence: how does the answer build from smaller indices?
- Base cases first; optimize space if only prior row/layer is needed.
- Define state: what subproblem does
Walkthrough — input low = 3, high = 3, zero = 1, one = 1, expected output 8:
One possible valid good string is “011”. It can be constructed as follows: “” -> “0” -> “01” -> “011”. All binary strings from “000” to “111” are good strings in this example.
| Approach | Time Complexity | Space Complexity | |———-|—————-|——————| | Bottom-Up DP | O(high) | O(high) | | Top-Down DP | O(high) | O(high) |
Algorithm Breakdown
Solution 1: Bottom-Up DP
class Solution {
List<Integer> dp = new ArrayList<>();
int MOD = 1e9 + 7;
public int dfs(int zero, int one, int end) {
if(dp[end] != -1) return dp[end];
int cnt = 0;
if(end >= one) cnt = (cnt + dfs(zero, one, end - one)) % MOD;
if(end >= zero) cnt = (cnt + dfs(zero, one, end - zero)) % MOD;
dp[end] = cnt;
return dp[end];
}
public int countGoodStrings(int low, int high, int zero, int one) {
dp.resize(high + 1, -1);
dp[0] = 1; // Base case: empty String
int rtn = 0;
for(int len = low; len <= high; len++) {
rtn = (rtn + dfs(zero, one, len)) % MOD;
}
return rtn;
}
}
Process:
- Initialize DP array with size
high + 1 - Set base case:
dp[0] = 1 - For each length
i, add ways fromi - zeroandi - one - Sum all valid lengths from
lowtohigh
Solution 2: Top-Down DP (Memoization)
class Solution {
public int countGoodStrings(int low, int high, int zero, int one) {
return dfs = new return(0, low, high, zero, one);
}
public int dfs(int len, int low, int high, int zero, int one) {
if (len > high) return 0;
int count = (len >= low) ? 1 : 0;
count = (count + dfs(len + zero, low, high, zero, one)) % MOD;
count = (count + dfs(len + one, low, high, zero, one)) % MOD;
return count;
}
int MOD = 1e9 + 7;
}
Process:
- Initialize DP array with
-1(unvisited) - Set base case:
dp[0] = 1 - For each length, recursively calculate using memoization
- Sum all valid lengths from
lowtohigh
Complexity
| Approach | Time Complexity | Space Complexity | |———-|—————-|——————| | Bottom-Up DP | O(high) | O(high) | | Top-Down DP | O(high) | O(high) |
Common Mistakes
- Single length range:
low = high = 1→ Check ifzero = 1orone = 1 - Large values:
high = 10^5→ Use modulo arithmetic - Equal zero and one:
zero = one = 1→ Standard binary strings -
Different zero and one:
zero = 1, one = 2→ Mixed length increments - Missing base case: Forgetting
dp[0] = 1 - Wrong recurrence: Not checking bounds
i - zero >= 0 - Modulo errors: Not applying modulo consistently
- Index bounds: Accessing
dp[i]without checking bounds
Detailed Example Walkthrough
Example: low = 2, high = 3, zero = 1, one = 2
Bottom-Up DP:
Step 1: Initialize
dp = [1, 0, 0, 0]
Step 2: Calculate dp[1]
i = 1, zero = 1, one = 2
- i - zero = 0 >= 0 → dp[1] += dp[0] = 1
- i - one = -1 < 0 → skip
dp = [1, 1, 0, 0]
Step 3: Calculate dp[2]
i = 2, zero = 1, one = 2
- i - zero = 1 >= 0 → dp[2] += dp[1] = 1
- i - one = 0 >= 0 → dp[2] += dp[0] = 1
dp = [1, 1, 2, 0]
Step 4: Calculate dp[3]
i = 3, zero = 1, one = 2
- i - zero = 2 >= 0 → dp[3] += dp[2] = 2
- i - one = 1 >= 0 → dp[3] += dp[1] = 1
dp = [1, 1, 2, 3]
Step 5: Sum from low to high
Sum = dp[2] + dp[3] = 2 + 3 = 5
Top-Down DP:
dfs(1, 2, 2):
- dp[2] = -1, calculate
- dfs(1, 2, 1) + dfs(1, 2, 0) = 1 + 1 = 2
- dp[2] = 2
dfs(1, 2, 3):
- dp[3] = -1, calculate
- dfs(1, 2, 2) + dfs(1, 2, 1) = 2 + 1 = 3
- dp[3] = 3
Sum = dp[2] + dp[3] = 2 + 3 = 5
Related Problems
Why These Solutions are Optimal
- Optimal Time Complexity: O(high) is the best possible
- Space Efficient: O(high) space usage
- Mathematical Insight: Converts problem to counting paths
- Modulo Handling: Properly handles large numbers
- Two Approaches: Both bottom-up and top-down are valid
References
- LC 2466: Count Ways To Build Good Strings on LeetCode
- LeetCode Discuss — LC 2466: Count Ways To Build Good Strings
- LeetCode Editorial (may require premium)
Key Takeaways
- DP State:
dp[i]= number of ways to build string of lengthi - Recurrence: Add ways from previous valid lengths
- Base Case: Empty string has 1 way
- Modulo: Handle large numbers with
10^9 + 7