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 that can be constructed satisfying these properties. Since the answer can be large, return it modulo 109 + 7.
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 <= 1051 <= zero, one <= low
Approach 01:
-
C++
-
Python
class Solution {
public:
int MOD = 1000000007;
vector<int> memo;
int solve(int low, int high, int zero, int one, int currentLength) {
if (currentLength > high) {
return 0;
}
if(memo[currentLength] != -1){
return memo[currentLength];
}
int total = 0;
// Base case: Valid string length
if (currentLength >= low && currentLength <= high) {
total = 1;
}
// Recursive case: Add zeros and ones
int left = solve(low, high, zero, one, currentLength + zero);
int right = solve(low, high, zero, one, currentLength + one);
total = (total + left + right) % MOD;
memo[currentLength] = total;
return total;
}
int countGoodStrings(int low, int high, int zero, int one) {
memo.assign(high+1, -1);
return solve(low, high, zero, one, 0);
}
};
class Solution:
MOD = 10**9 + 7
def solve(self, low, high, zero, one, currentLength):
if currentLength > high:
return 0
# Memoization check
if self.memo[currentLength] != -1:
return self.memo[currentLength]
# Base case: valid string within the range
total = 0
if low <= currentLength <= high:
total = 1
# Recursive case: add strings of zeros and ones
left = self.solve(low, high, zero, one, currentLength + zero)
right = self.solve(low, high, zero, one, currentLength + one)
total = (total + left + right) % self.MOD
self.memo[currentLength] = total # Store result in memo
return total
def countGoodStrings(self, low: int, high: int, zero: int, one: int) -> int:
# Initialize memoization table
self.memo = [-1] * (high + 1)
return self.solve(low, high, zero, one, 0)
Time Complexity:
- Memoization:
- Each unique state, defined by
currentLength, is computed only once due to memoization. - The number of possible states is proportional to \( O(\text{high}) \), where \(\text{high}\) is the maximum allowable length.
- Each unique state, defined by
- Overall Time Complexity for Recursive Approach:
The total time complexity is \( O(\text{high}) \), as each state is visited exactly once and operations for each state are \( O(1) \).
Space Complexity:
- Memoization Table:
- A table of size \( O(\text{high}) \) is maintained to store the results for each state of
currentLength.
- A table of size \( O(\text{high}) \) is maintained to store the results for each state of
- Recursive Stack Depth:
- In the worst case, the recursive stack depth can go up to \( O(\text{high}) \), as each recursive call decreases
currentLengthby at least 1.
- In the worst case, the recursive stack depth can go up to \( O(\text{high}) \), as each recursive call decreases
- Overall Space Complexity:
The total space complexity is \( O(\text{high}) \), combining the memoization table and recursive stack space.
Approach 02:
-
C++
-
Python
class Solution {
public:
int MOD = 1000000007;
int countGoodStrings(int low, int high, int zero, int one) {
vector<int> dp(high+1, 0);
dp[0] = 1;
for(int length = 1; length <= high; ++length){
if(length>=zero){
dp[length] = (dp[length] + dp[length-zero])%MOD;
}
if(length>=one){
dp[length] = (dp[length] + dp[length-one])%MOD;
}
}
int result = 0;
for(int length=low; length<=high; ++length){
result = (result + dp[length])%MOD;
}
return result;
}
};
class Solution:
MOD = 1000000007
def countGoodStrings(self, low: int, high: int, zero: int, one: int) -> int:
# Initialize the dp array
dp = [0] * (high + 1)
dp[0] = 1 # Base case: 1 way to create a string of length 0
# Fill the dp array
for length in range(1, high + 1):
if length >= zero: # Add strings ending with `zero`
dp[length] = (dp[length] + dp[length - zero]) % self.MOD
if length >= one: # Add strings ending with `one`
dp[length] = (dp[length] + dp[length - one]) % self.MOD
# Calculate the result for lengths within [low, high]
result = 0
for length in range(low, high + 1):
result = (result + dp[length]) % self.MOD
return result
Time Complexity:
- DP Calculation:
- We iterate through all values up to \( \text{high} \) to compute the DP array values.
- The operations for each value are \( O(1) \), so the total computation is \( O(\text{high}) \).
- Result Calculation:
- Summing up valid lengths from the DP table requires a single pass over the array, which is \( O(\text{high}) \).
- Overall Time Complexity for DP Approach:
The total time complexity is \( O(\text{high}) \).
Space Complexity:
- DP Array:
- The DP array has a size of \( O(\text{high}) \), storing values for all lengths up to \( \text{high} \).
- Overall Space Complexity:
The total space complexity is \( O(\text{high}) \), as only the DP array is stored.