2450. Number of Distinct Binary Strings After Applying Operations

MediumMathString
Leetcode Link

Problem Description

In this problem, we have a binary string s, meaning it contains only characters 0 and 1. Along with this binary string, we're given a positive integer k. We're allowed to perform a specific operation on the string as many times as we wish, which is to choose any substring of length k and flip all the characters within it (i.e., turn 1s into 0s and 0s into 1s).

Our goal is to determine the number of distinct binary strings we can produce from string s by applying the said operation any number of times. Since this number can be large, our final answer must be presented modulo 10^9 + 7.

In terms of definitions here, a binary string is simply a sequence of 0s and 1s. A substring refers to a sequence of characters that are consecutive within the larger string.

Intuition

Let's dive into the intuition behind the provided solution:

We are dealing with a binary string, and flipping operations always include substrings of the same size, k. Notice that once we pick a particular substring to flip, the result is independent of the characters outside of this substring. This means that each substring can be considered in isolation. Also notable is that flipping a substring twice will revert it to the original state. Hence, each unique substring of length k allows for 2 distinct outcomes: flipped and unflipped.

Now, consider the unique substrings of length k that can be obtained from s. If s has n characters, there are n - k + 1 possible distinct substrings of length k. Each of these substrings can be in one of two states after a flip operation: their original state, or their flipped state. This leads us to the conclusion that there are at most 2^(n - k + 1) distinct strings that can be obtained after performing any number of flip operations on the binary string s.

We say "at most" because if k is equal to the length of s, no matter how many times we operate, there will only be 2 distinct strings, the original and its complete flip. The formula accounts for this because when k equals the length of s, n - k + 1 equals 1, indicating just two options.

The python solution appears to be a direct implementation of this intuition:

1def countDistinctStrings(self, s: str, k: int) -> int:
2    return pow(2, len(s) - k + 1) % (10**9 + 7)

Thus, we use the pow function in Python to calculate 2^(n - k + 1) and then take the modulo % with 10^9 + 7 to ensure the answer fits within the proper range.

This solution assumes that all substrings of length k within s can be independently flipped to create new combinations. The essence of the solution hinges upon the concept of binary choice for every substring of length k, which, when combined, leads to the exponential number of distinct strings attainable through the operation.

Learn more about Math patterns.

Not Sure What to Study? Take the 2-min Quiz to Find Your Missing Piece:

What's the output of running the following function using input [30, 20, 10, 100, 33, 12]?

1def fun(arr: List[int]) -> List[int]:
2    import heapq
3    heapq.heapify(arr)
4    res = []
5    for i in range(3):
6        res.append(heapq.heappop(arr))
7    return res
8
1public static int[] fun(int[] arr) {
2    int[] res = new int[3];
3    PriorityQueue<Integer> heap = new PriorityQueue<>();
4    for (int i = 0; i < arr.length; i++) {
5        heap.add(arr[i]);
6    }
7    for (int i = 0; i < 3; i++) {
8        res[i] = heap.poll();
9    }
10    return res;
11}
12
1class HeapItem {
2    constructor(item, priority = item) {
3        this.item = item;
4        this.priority = priority;
5    }
6}
7
8class MinHeap {
9    constructor() {
10        this.heap = [];
11    }
12
13    push(node) {
14        // insert the new node at the end of the heap array
15        this.heap.push(node);
16        // find the correct position for the new node
17        this.bubble_up();
18    }
19
20    bubble_up() {
21        let index = this.heap.length - 1;
22
23        while (index > 0) {
24            const element = this.heap[index];
25            const parentIndex = Math.floor((index - 1) / 2);
26            const parent = this.heap[parentIndex];
27
28            if (parent.priority <= element.priority) break;
29            // if the parent is bigger than the child then swap the parent and child
30            this.heap[index] = parent;
31            this.heap[parentIndex] = element;
32            index = parentIndex;
33        }
34    }
35
36    pop() {
37        const min = this.heap[0];
38        this.heap[0] = this.heap[this.size() - 1];
39        this.heap.pop();
40        this.bubble_down();
41        return min;
42    }
43
44    bubble_down() {
45        let index = 0;
46        let min = index;
47        const n = this.heap.length;
48
49        while (index < n) {
50            const left = 2 * index + 1;
51            const right = left + 1;
52
53            if (left < n && this.heap[left].priority < this.heap[min].priority) {
54                min = left;
55            }
56            if (right < n && this.heap[right].priority < this.heap[min].priority) {
57                min = right;
58            }
59            if (min === index) break;
60            [this.heap[min], this.heap[index]] = [this.heap[index], this.heap[min]];
61            index = min;
62        }
63    }
64
65    peek() {
66        return this.heap[0];
67    }
68
69    size() {
70        return this.heap.length;
71    }
72}
73
74function fun(arr) {
75    const heap = new MinHeap();
76    for (const x of arr) {
77        heap.push(new HeapItem(x));
78    }
79    const res = [];
80    for (let i = 0; i < 3; i++) {
81        res.push(heap.pop().item);
82    }
83    return res;
84}
85

Solution Approach

The solution approach for this problem is straightforward and does not involve complex algorithms or data structures. In fact, it elegantly demonstrates the power of combinatorics in algorithmics.

Here is what the solution does step by step:

  1. Problem Analysis: The first step is the recognition that each k-length substring has two states when flipped (original and flipped). Since we have n - k + 1 potential unique substrings, we simply calculate the number of different combinations possible by using powers of 2.

  2. Mathematical Foundation: The combinatorial principal applied here is represented by the expression 2^(n - k + 1). For each of the n - k + 1 substrings, there are 2 possibilities after a flip operation. Thus, the total possibilities are the product of all these independent possibilities (2 multiplied by itself n - k + 1 times).

  3. Implementation: The Python pow function is used to calculate this power of 2. The pow function offers a built-in, computationally efficient way to calculate powers, even for large exponents. The expression is:

    1pow(2, len(s) - k + 1)

    This calculates 2 raised to the power of len(s) - k + 1.

  4. Modulo Operation: Since the problem specifies that the output should be modulo 10^9 + 7, we use the modulo operator %. In Python, this is as simple as appending % (10**9 + 7) to the pow function call. This operation ensures that the result is within the specified range, which is a common requirement to avoid overflow in problems dealing with combinatorics and large numbers.

The overall solution is concise because the problem does not require generation or enumeration of each distinct string. It just asks for the count, which allows for an elegant mathematical shortcut. The lack of Reference Solution Approach leaves the implementation part relatively straightforward given the direct translation of the math into code.

The final solution code is:

1class Solution:
2    def countDistinctStrings(self, s: str, k: int) -> int:
3        return pow(2, len(s) - k + 1, 10**9 + 7)

Notice the added third parameter to the pow function, which takes care of the modulo operation in a more efficient manner, potentially reducing the overhead of large number manipulation by performing the modulo at each step of power multiplication.

No additional data structures are used or needed, and no traditional algorithms are in play here; it’s primarily a demonstration of understanding exponential growth and its representation in code.

Discover Your Strengths and Weaknesses: Take Our 2-Minute Quiz to Tailor Your Study Plan:

Which algorithm should you use to find a node that is close to the root of the tree?

Example Walkthrough

Let's go through an example to understand how the solution works.

Suppose our binary string s is "10101" and our integer k is 2. This means we are looking at every possible unique substring of length 2 that can be flipped to get a new combination.

Original string s: "10101"

All unique substrings of length 2: "10", "01", "10", "01"

  • For substring "10", by flipping, we can get "01".
  • For substring "01", by flipping, we can get "10".

There are 4 unique substrings of length 2 since n = 5 and k = 2 (n - k + 1 = 5 - 2 + 1 = 4). For each of these substrings, we can have two states either original or flipped.

This corresponds to 2^4 combinations because we have 2 choices (flip or no flip) for each of the 4 places where a flip can occur.

Hence, according to our solution approach, we simply do:

1pow(2, 4) % (10**9 + 7)  # Using the corresponding values of n and k for this example

This evaluates to:

116 % (10**9 + 7)  # Which equals 16 since 16 is much less than 10**9 + 7

So, there are 16 distinct binary strings that can be produced by flipping any of the substrings of length k=2 from our original string s="10101".

The final answer in this example would be 16. This example illustrates how the provided solution calculates the number of distinct binary strings efficiently.

Solution Implementation

1class Solution:
2    def countDistinctStrings(self, s: str, k: int) -> int:
3        # Calculates the number of distinct substrings of length k
4        # by computing 2 to the power of the difference between
5        # the string's length and k, then adding 1. The result is
6        # then modulo by 10^9 + 7 to ensure it does not exceed that value.
7      
8        # Calculate the total count of distinct possible strings
9        total_count = pow(2, len(s) - k + 1)
10      
11        # Take modulo with 10^9 + 7 to keep the number within the integer limit
12        mod_total_count = total_count % (10**9 + 7)
13      
14        return mod_total_count  # Return the result after modulo operation
15
1class Solution {
2
3    // Create a constant for the modulo operation
4    // to avoid counting numbers larger than 1e9 + 7
5    public static final int MODULO = (int) 1e9 + 7;
6
7    /**
8     * Count the number of distinct substrings of length k that can be generated.
9     *
10     * @param str The input string to process.
11     * @param k The length of substrings to account for.
12     * @return The count of distinct substrings modulo 1e9 + 7.
13     */
14    public int countDistinctStrings(String str, int k) {
15        // Initialize the answer to 1, considering an empty string to start with
16        int distinctSubstringCount = 1;
17
18        // Iterate over each possible starting position for substrings of length k
19        for (int i = 0; i <= str.length() - k; ++i) {
20            // Each character can either be included or not in each of the k length window,
21            // effectively doubling the possibilities every iteration.
22            // Take a modulo to keep the value within the bounds of MODULO.
23            distinctSubstringCount = (distinctSubstringCount * 2) % MODULO;
24        }
25
26        // Return the number of distinct substrings
27        return distinctSubstringCount;
28    }
29}
30
1#include <string>
2
3class Solution {
4public:
5    // Define the modulus for large number handling
6    static constexpr int MOD = 1e9 + 7;
7
8    // Function to count the number of distinct substrings of length k in string s
9    int countDistinctStrings(std::string s, int k) {
10        // Initialize answer to '1', as we start with a single-character string
11        int distinctCount = 1;
12      
13        // Iterate over the string to consider all possible substrings of length k
14        for (int i = 0; i <= s.size() - k; ++i) {
15            // Double the count for each character considered, modulo MOD
16            distinctCount = (distinctCount * 2) % MOD;
17        }
18      
19        // Return the final count of distinct substrings
20        return distinctCount;
21    }
22};
23
1// Define the modulus for large number handling
2const MOD: number = 1e9 + 7;
3
4// Function to count the number of distinct substrings of length k in string s
5function countDistinctStrings(s: string, k: number): number {
6    // Initialize the count of distinct substrings
7    let distinctCount: number = 1;
8  
9    // Iterate over the string to consider all possible substrings of length k
10    for (let i = 0; i <= s.length - k; i++) {
11        // Double the count for each substring considered, modulo MOD
12        distinctCount = (distinctCount * 2) % MOD;
13    }
14  
15    // Return the final count of distinct substrings
16    return distinctCount;
17}
18
Not Sure What to Study? Take the 2-min Quiz:

In a binary min heap, the maximum element can be found in:

Time and Space Complexity

The time complexity of the function is O(1) because it consists of only one operation: calculating the power of 2 which is done in constant time regardless of the input size.

The space complexity of the function is also O(1) since the space used does not depend on the input size and only uses a fixed amount of space for intermediate calculations and to store the result.

Learn more about how to find time and space complexity quickly using problem constraints.

Fast Track Your Learning with Our Quick Skills Quiz:

Which of these pictures shows the visit order of a depth-first search?


Recommended Readings


Got a question? Ask the Teaching Assistant anything you don't understand.

Still not clear? Ask in the Forum,  Discord or Submit the part you don't understand to our editors.


TA 👨‍🏫