Count Ways To Build Good Strings

Updated on 16 May, 2025
Count Ways To Build Good Strings header image

Problem Statement

In this problem, you are given four integers: zero, one, low, and high. The problem revolves around constructing binary strings that start from an empty string. At each construction step, you can choose to:

  1. Append the character '0', exactly zero times to the current string.
  2. Append the character '1', exactly one times to the string.

These operations can be executed any number of times sequentially in any order. A string is classified as good if its total length lies between the range defined by low and high (inclusive).

Your goal is to compute the total number of distinct good strings that can be created using the above rules. Due to potentially large outcomes, the final result should be presented 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 <= 105
  • 1 <= zero, one <= low

Approach and Intuition

To tackle the problem effectively, let's dissect how strings are constructed and determine their uniqueness based on the provided examples:

Example 1

Input Parameters:

  • low = 3, high = 3
  • zero = 1, one = 1

Output:

  • Total good strings: 8

Steps and Strategy:

  • For this input scenario, all possible binary strings of length 3 ("000" to "111") meet the conditions for being good strings because the conditions allow adding either one '0' or one '1' to the string repeatedly.

  • The strategy involves generating combinations of the characters '0' and '1', adhering strictly to the limits of low and high while respecting the specific repetition allowed for '0' and '1' (which is 1 in both cases here). This makes it straightforward since every possible combination is valid.

Example 2

Input Parameters:

  • low = 2, high = 3
  • zero = 1, one = 2

Output:

  • Total good strings: 5

Steps and Strategy:

  • The focus here shifts to variations between lengths of 2 and 3:

    • For length 2, the strings "00" and "11" are valid (note: '1' is appended twice, so "11").
    • For length 3, the valid strings are "000", "110", and "011".
  • This example illustrates that not every binary permutation is valid, but rather those that respect the number of repetitions detailed by zero and one parameters.

In a generalized approach:

  • Define the function to recursively generate strings based on the specified constraints, accounting for low to high string lengths.
  • Ensure that sequences are unique through the mixture of '0's and '1's as prescribed, tracking instances where the character count matches the allowed append count.
  • Maintain a count of such unique sequences within the defined bounds and utilize modulo arithmetic to handle large numbers as required by problem constraints.

Through these steps, the problem translates into recursive combinations and modulate counting in adherence to the problem's strict rules.

Solutions

  • Java
  • Python
java
class Solution {
    int[] memo;
    int modulo = 1_000_000_007;
    
    private int countSequences(int length, int zeros, int ones) {
        if (memo[length] != -1) {
            return memo[length];
        }
        
        int result = 0;
        if (length >= ones) {
            result += countSequences(length - ones, zeros, ones);
        }
        if (length >= zeros) {
            result += countSequences(length - zeros, zeros, ones);
        }
        memo[length] = result % modulo;
        return memo[length];
    }
    
    public int findNumberOfGoodStrings(int minLen, int maxLen, int zeros, int ones) {
        memo = new int[maxLen + 1];
        Arrays.fill(memo, -1);
        memo[0] = 1;

        int sum = 0;
        for (int i = minLen; i <= maxLen; ++i) {
            sum += countSequences(i, zeros, ones);
            sum %= modulo;
        }
        return sum;
    }
}

The provided Java code defines a class Solution with methods to count ways to build "good" strings given certain constraints on the number of zeros and ones that can be used. The main functionality is split between two methods.

  • countSequences(int length, int zeros, int ones): This method calculates the number of valid sequences of a given length using a recursive approach with memoization. It checks if the provided memoization array already contains the solution for a given length to avoid redundant calculations. The recursion considers strings formed by subtracting the number of ones and zeros from the current length and sums these recursive calls, considering them modulo 1_000_000_007 to handle large numbers.

  • findNumberOfGoodStrings(int minLen, int maxLen, int zeros, int ones): This method initializes the memoization array and iterates through all lengths from minLen to maxLen. For each length, it adds up the count of valid sequences obtained from countSequences, again taking results modulo 1_000_000_007. The sum of all these counts for each length provides the total number of good strings within the specified range.

The solution utilizes dynamic programming to efficiently compute the number of sequences for different lengths by storing intermediate results and avoiding repetitive calculations. Each call to countSequences ensures results are modulo 1_000_000_007, a common approach in problems dealing with large numbers to prevent overflow and maintain performance. The memoization helps in significantly reducing the time complexity that would otherwise result from the naive recursive solution.

python
class Solution:
    def countValidStrings(self, minLen: int, maxLen: int, lenX: int, lenY: int) -> int:
        # Utilize memoization to store counts of valid strings for different lengths
        memo = [1] + [-1] * (maxLen)
        modulus = 1000000007
        
        # Recursive function to determine counts for specific lengths
        def computeCounts(length):
            if memo[length] != -1:
                return memo[length]
            currentCount = 0
            if length >= lenX:
                currentCount += computeCounts(length - lenX)
            if length >= lenY:
                currentCount += computeCounts(length - lenY)
            memo[length] = currentCount % modulus
            return memo[length]
            
        # Sum up counts for all lengths in the required range
        return sum(computeCounts(length) for length in range(minLen, maxLen + 1)) % modulus

This Python solution involves counting the number of "good" strings that can be built within given constraints on their length. The task is approached using dynamic programming with a memoization technique to efficiently solve the problem without recalculating results for the same input parameters.

  • Initialize a memoization list memo where each index corresponds to a length of the string and stores the number of valid strings of that length. All indices are initially set to -1, except for the 0th index, which is 1 (representing the count of an empty string).
  • Define a modulus modulus = 1000000007 to ensure the results for string counts are within manageable size by taking results modulo this large prime.
  • Implement a recursive function computeCounts that:
    • Checks if the result for a given length is already computed by looking up memo.
    • Recursively calculates the number of valid strings of a particular length by summing the counts of strings reduced by lenX and lenY (if applicable), ensuring the problem's constraints (minimum and maximum lengths) are honored.
  • Sum the valid string counts between minLen and maxLen, inclusive, using a generator expression that invokes computeCounts for each desired length.
  • Use the modulo operation to ensure the final sum remains within numeric limits.

Overall, this solution leverages memoization and recursion to tackle the potential exponential growth in computation that could otherwise result from the many possible string combinations, effectively reducing complexity and increasing performance.

Comments

No comments yet.