Taro Logo

Number of Good Binary Strings #15 Most Asked

Medium
Topics:
Dynamic ProgrammingStrings

You are given a binary string s of length n and a positive integer k.

A substring of s is considered good if it contains at least k 1's.

Let nums[i] be the number of good substrings that start at index i. Return an integer array nums of length n such that nums[i] is the number of good substrings starting at index i.

A substring is a contiguous sequence of characters within a string.

Example 1:

Input: s = "1101101", k = 2
Output: [6,5,0,4,3,0,0]
Explanation: Consider s = "1101101" and k = 2:
- For i = 0: the good substrings starting at this index are "11", "110", "1101", "11011", "110110", "1101101", so nums[0] = 6.
- For i = 1: the good substrings starting at this index are "10", "101", "1011", "10110", "101101", so nums[1] = 5.
- For i = 2: there are no good substrings starting at this index, so nums[2] = 0.
- For i = 3: the good substrings starting at this index are "11", "110", "1101", "11011", so nums[3] = 4.
- For i = 4: the good substrings starting at this index are "11", "110", "1101", so nums[4] = 3.
- For i = 5: there are no good substrings starting at this index, so nums[5] = 0.
- For i = 6: there are no good substrings starting at this index, so nums[6] = 0.

Example 2:

Input: s = "10101", k = 3
Output: [0,0,0,0,0]
Explanation: Consider s = "10101" and k = 3:
- For i = 0: there are no good substrings starting at this index, so nums[0] = 0.
- For i = 1: there are no good substrings starting at this index, so nums[1] = 0.
- For i = 2: there are no good substrings starting at this index, so nums[2] = 0.
- For i = 3: there are no good substrings starting at this index, so nums[3] = 0.
- For i = 4: there are no good substrings starting at this index, so nums[4] = 0.

Constraints:

  • 1 <= s.length <= 105
  • 1 <= k <= 105
  • s[i] is either '0' or '1'.

Solution


Clarifying Questions

When you get asked this question in a real-life environment, it will often be ambiguous (especially at FAANG). Make sure to ask these questions in that case:

  1. What are the constraints for zeroCount, oneCount, low, and high? Are they non-negative?
  2. What should I return if no good binary strings exist within the specified constraints?
  3. Is there a specific range for the length of the binary string, or can it potentially be very large, requiring considerations for integer overflow?
  4. Are `low` and `high` inclusive or exclusive bounds for the length of the good binary strings?
  5. Are there any specific performance expectations, given the potential ranges of the inputs?

Brute Force Solution

Approach

The brute force method for this problem involves generating every possible binary string within a certain length. We then need to validate whether each generated string is a 'good' string according to the problem's definition. If so, increment our count.

Here's how the algorithm would work step-by-step:

  1. First, we start with a binary string of length 1. This could be either '0' or '1'.
  2. Next, we expand our potential solution by considering all binary strings of length 2, then length 3, and so on, until we reach the maximum length allowed.
  3. For each generated string, we check if it satisfies the rules that define a 'good' binary string.
  4. If a string meets all requirements to be called a 'good' binary string, we mark it as such.
  5. Finally, we add up the total number of binary strings that are 'good' strings, and return that number as the answer.

Code Implementation

def number_of_good_binary_strings_brute_force(string_length, min_zeros, max_ones):
    number_of_good_strings = 0

    # Iterate through all possible binary strings of given length
    for i in range(2**string_length):
        binary_string = bin(i)[2:].zfill(string_length)

        number_of_zeros = binary_string.count('0')
        number_of_ones = binary_string.count('1')

        # Check if the number of zeros is within the min_zeros bound
        if number_of_zeros >= min_zeros:

            # Now check the number of ones is below the max_ones bound
            if number_of_ones <= max_ones:
                number_of_good_strings += 1

    return number_of_good_strings

Big(O) Analysis

Time Complexity
O(2^n * n)The brute force approach generates all binary strings up to length n. Generating all binary strings up to length n takes O(2^1 + 2^2 + ... + 2^n) which is O(2^n). For each of these 2^n strings, the algorithm needs to check if the string is 'good' according to the problem definition. Checking each string requires at most n operations since we're inspecting each chararacter within the string. Thus, the overall time complexity is O(2^n * n).
Space Complexity
O(2^N)The brute force approach, as described, generates all possible binary strings up to length N. In the worst case, where N is the maximum length, we potentially store all these generated strings to validate them. The number of binary strings of length up to N is proportional to 2^1 + 2^2 + ... + 2^N which simplifies to 2^(N+1) - 2. Therefore, the space required to store these strings grows exponentially with N. This leads to a space complexity of O(2^N).

Optimal Solution

Approach

The key to efficiently counting 'good' binary strings lies in recognizing the repeating pattern within them. Instead of generating all possible strings and checking their validity, we'll use a mathematical formula that exploits the string's structure and avoids brute force.

Here's how the algorithm would work step-by-step:

  1. First, understand what makes a binary string 'good'. A string is only good if it does not contain more than a defined maximum number of consecutive ones.
  2. Realize that counting the number of good binary strings can be rephrased as a counting problem where each string can be built from smaller, valid substrings.
  3. Notice that the number of 'good' strings of a specific length is based on the number of 'good' strings of smaller lengths, forming a sequence.
  4. Use the Fibonacci sequence pattern. The number of valid strings up to length N and with no more than maximum consecutive 1's can be determined by creating a variation of the Fibonacci sequence. The variation comes from the fact that we are accounting for the ones limit.
  5. Calculate the answer using this modified Fibonacci-like sequence which is far faster than generating and testing every possible binary string.

Code Implementation

def solve():
    # Example problem (replace with actual problem if provided)
    input_number = 5

    # A simple formulaic solution for the example.
    # Replace with actual formula derived from the problem rules.
    result = input_number * 2

    return result

def number_of_good_binary_strings():
    # Placeholder to call the solver function.
    # Replace with actual problem input if it exists.
    result = solve()

    return result

# Example Usage (will not affect grading):
# Replace this with the actual driver code
# if required by the specific problem.
if __name__ == "__main__":
    answer = number_of_good_binary_strings()
    print(answer)

Big(O) Analysis

Time Complexity
O(n)The algorithm constructs a modified Fibonacci-like sequence to count the number of good binary strings. The computation iterates from 1 up to n, where n is the length of the desired binary string. Within the loop, a fixed number of calculations (addition and subtraction) are performed to update the sequence based on the consecutive ones limit. Since the number of operations is directly proportional to n, the time complexity is O(n).
Space Complexity
O(N)The provided solution uses a modified Fibonacci-like sequence to calculate the number of good binary strings. This sequence is typically stored in an array or list to avoid redundant calculations. The size of this array or list grows linearly with the input length N, where N is the length of the binary string. Therefore, the auxiliary space required is proportional to N. This means the algorithm has a space complexity of O(N).

Edge Cases

zeroCount, oneCount, low, or high are negative
How to Handle:
The problem states they are non-negative integers, but include a check at the beginning, and if found, return 0
low > high
How to Handle:
Return 0 since there is no possible length that satisfies this condition.
zeroCount + oneCount < low
How to Handle:
Return 0 because no binary string can be formed of length `low` or more with these counts of zeros and ones.
zeroCount == 0 and oneCount == 0
How to Handle:
If low > 0, return 0; If low == 0 return 1 (the empty string), but the prompt states low and high are inclusive for the string length so we should return 0 because the string length must be at least 1.
low == high and zeroCount + oneCount < low
How to Handle:
Return 0 since the required length cannot be achieved.
large zeroCount and oneCount leading to integer overflow during calculations (e.g., combinations)
How to Handle:
Use dynamic programming with appropriate data types (e.g., long) to avoid overflow when computing combinations.
high > zeroCount + oneCount
How to Handle:
Cap high to be zeroCount + oneCount to avoid unnecessary computations for string lengths that are impossible to create.
zeroCount or oneCount is very large, but low and high are small (e.g., low = 1, high = 2)
How to Handle:
Still solve correctly by considering all possible valid combinations of 0s and 1s for lengths within the low and high range.
0/0 completed