Count Vowel Substrings of a String

The solution to this problem consists of several building blocks:

  1. Sliding Window Technique: The sliding window technique is a method for finding subarrays in an array or substrings in a string that meet a certain condition. It involves maintaining a “window” and adjusting its size and position based on the given condition. In this problem, the window represents a substring of the input string.

  2. Dictionary for Character Counting: This problem uses a dictionary to count the occurrence of each vowel in the current window. The keys in the dictionary are the vowel characters, and the values are their counts.

  3. Tracking Unique Vowels: The number of unique vowels in the current window is tracked using a separate variable. This is done by incrementing the variable whenever a vowel is encountered for the first time in the current window.

  4. Counting Substrings: The solution involves counting the number of substrings that contain all five vowels. This is done by maintaining a count variable and incrementing it based on the start position of the substring and the current position in the string.

  5. Handling Non-Vowel Characters: When a non-vowel character is encountered, the solution resets the dictionary and the count of unique vowels, and moves the start of the substring to the next character.

These building blocks come together to form the complete solution. They demonstrate techniques such as the sliding window technique and dictionary usage, and concepts such as character counting and substring counting.

The building blocks of the solution to count vowel substrings of a string are:

  • Tracking the number of vowels in a substring. This can be done using a dictionary or a set. For example, you can create a dictionary where the keys are the vowels and the values are the number of times each vowel appears in the substring.
  • Iterating through the string and updating the vowel count. This can be done using a for loop. For example, you can iterate through the string and increment the vowel count for each vowel that appears in the string.
  • Determining if the substring has 5 vowels. This can be done by checking if the value of the vowel count is equal to 5.
  • Counting the number of vowel substrings. This can be done by iterating through the string and counting the number of times the substring has 5 vowels.

The solution is built by combining these building blocks. For example, the track_vowels() function tracks the number of vowels in a substring by using a dictionary. The update_vowel_count() function updates the vowel count by iterating through the string and incrementing the vowel count for each vowel that appears in the string. The has_five_vowels() function determines if the substring has 5 vowels by checking if the value of the vowel count is equal to 5. The count_vowel_substrings() function counts the number of vowel substrings by iterating through the string and counting the number of times the substring has 5 vowels.

The solution is efficient because it uses the track_vowels() function to track the number of vowels in a substring. This means that the update_vowel_count() and has_five_vowels() functions only need to iterate through the string once. This makes the solution efficient because it does not need to iterate through the string multiple times.

The solution is also easy to understand because it uses simple building blocks. The building blocks are easy to understand because they are well-known and well-documented. This makes the solution easy to understand because it does not use any complex or obscure concepts.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
class Solution:
    def countVowelSubstrings(self, word: str) -> int:
        vow, cnt, m = 0, 0, {chr(i): 0 for i in range(123)}
        vowels = "aeiou"

        i, j, k = 0, 0, 0
        while i < len(word):
            if word[i] in vowels:
                m[word[i]] += 1
                if m[word[i]] == 1:
                    vow += 1
                while vow == 5:
                    m[word[k]] -= 1
                    if m[word[k]] == 0:
                        vow -= 1
                    k += 1
                cnt += k - j
            else:
                m = {chr(i): 0 for i in range(123)}
                vow = 0
                j = k = i + 1
            i += 1

        return cnt

[j, i] is a string with all vowels. [k, i] is the shortest substring containing all 5 vowels. Therefore, for position i, we have k - j substrings with 5 vowels.

The countVowelSubstrings function in the provided Python code calculates the number of substrings in a given word that contain all five vowels (a, e, i, o, u). This is accomplished using a sliding window and a dictionary to count the occurrence of each vowel.

Let’s break down this function:

  • vow: This variable keeps track of the number of unique vowels seen so far in the current sliding window.
  • cnt: This variable keeps track of the total count of substrings that contain all five vowels.
  • m: This is a dictionary that maps each ASCII character to its count within the current window. The chr(i): 0 for i in range(123) initialization sets the count of all ASCII characters (up to ‘z’) to 0.
  • vowels = "aeiou": This is a string containing all vowels.
  • i, j, k = 0, 0, 0: These are indices used for managing the sliding window.

The function iterates over the input word. For each character:

  • If the character is a vowel, it increments the count in m and possibly increments vow if this is the first occurrence of this vowel in the current window. Then, while there are 5 unique vowels in the window (indicated by vow being 5), it reduces the size of the window from the left side by incrementing k and adjusting the count in m and possibly vow. It then increments cnt by the size of the window.
  • If the character is not a vowel, it resets m, vow, and the window indices j and k to start a new window after this character.

The function finally returns cnt, the total count of substrings that contain all five vowels.

This function takes advantage of the fact that a substring that ends at position i and contains all vowels can be extended to the left to form more such substrings. For example, if word is "aeiou", the substring "aeiou" ending at position 4 forms 5 substrings that contain all vowels: "aeiou", "eiou", "iou", "ou", and "u".

The three-pointer technique, also known as the sliding window technique, is a common strategy for solving array and string problems where we need to track a contiguous block of elements that satisfy some condition.

In this case, the problem is to find all substrings in a word that contain all five vowels. The condition we’re interested in is “the substring contains all five vowels”. We want to track a contiguous block (substring) of the word that satisfies this condition.

The three pointers, i, j, and k, serve different purposes:

  1. i (the right pointer): This pointer expands the window to the right. It is used to iterate over the characters of the string from left to right.

  2. j (the left boundary pointer): This pointer represents the starting index of the current substring. When a non-vowel character is encountered, j is moved to i + 1 to start a new substring from the character following the non-vowel.

  3. k (the contraction pointer): This pointer contracts the window from the left, but only when all vowels have been encountered in the current window. It is used to remove characters from the start of the current substring until the substring no longer contains all five vowels.

The three pointers allow the algorithm to maintain a valid window that contains all five vowels and to efficiently track the start of this window for counting substrings.

As for why we make the design choice of using this technique, it is because the sliding window technique is an efficient way to solve this kind of problem. The alternative would be to generate all substrings of the word and check each one to see if it contains all five vowels, which would be much less efficient. The three-pointer technique allows us to solve the problem in linear time.

The design choice of picking three pointers to solve this problem is based on the following considerations:

  • The problem requires us to track the number of vowels in a substring.
  • The defaultdict data structure is a good way to track the number of vowels in a substring, but it is not efficient for tracking the number of vowels in a substring that is constantly changing.
  • Three pointers can be used to track the beginning, end, and current position of the substring.
  • This allows us to efficiently track the number of vowels in the substring and to quickly determine if the substring has 5 vowels.

The three pointers are initialized as follows:

  • The i pointer points to the beginning of the substring.
  • The j pointer points to the end of the substring.
  • The k pointer points to the current position of the substring.

The algorithm then iterates through the string, updating the i, j, and k pointers as follows:

  • If the current character is a vowel, then the k pointer is incremented.
  • If the k pointer is equal to 5, then the algorithm increments the cnt variable.
  • If the current character is not a vowel, then the j pointer is incremented and the k pointer is reset to 0.

The algorithm terminates when the i pointer reaches the end of the string.

The design choice of picking three pointers to solve this problem is a good choice because it is efficient and easy to implement.

Here are some names for the three pointers:

  • i: The start pointer.
  • j: The end pointer.
  • k: The current pointer.

The start pointer points to the beginning of the substring. The end pointer points to the end of the substring. The current pointer points to the current position of the substring.

The names of the pointers are self-explanatory and make it easy to understand the code. They are also consistent with the names of the pointers used in other algorithms.

Here is an example of how the three pointers are used in the code:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
i, j, k = 0, 0, 0
while i < len(word):
    if word[i] in vowels:
        k += 1
        if k == 5:
            cnt += k - j
        j = k
    else:
        k = 0
        j = i + 1
    i += 1

In this code, the i pointer is incremented at the end of the loop. The j pointer is incremented when the current character is not a vowel. The k pointer is incremented when the current character is a vowel and is reset to 0 when the current character is not a vowel.

The names of the pointers make it easy to understand the code and to see how the pointers are used to track the number of vowels in the substring.

Visualization can be very helpful for understanding how algorithms work. Let’s consider an example word “aeiaou”. We will look at how the three pointers i, j, and k move during the execution of the algorithm.

aeiaou
i
j
k
vow = 1, cnt = 0
m = {'a': 1, 'e': 0, 'i': 0, 'o': 0, 'u': 0, ...}

aeiaou
 i
j
k
vow = 2, cnt = 0
m = {'a': 1, 'e': 1, 'i': 0, 'o': 0, 'u': 0, ...}

aeiaou
  i
j
k
vow = 3, cnt = 0
m = {'a': 1, 'e': 1, 'i': 1, 'o': 0, 'u': 0, ...}

aeiaou
   i
j
k
vow = 3, cnt = 0
m = {'a': 2, 'e': 1, 'i': 1, 'o': 0, 'u': 0, ...}

aeiaou
    i
j
k
vow = 4, cnt = 0
m = {'a': 2, 'e': 1, 'i': 1, 'o': 1, 'u': 0, ...}

aeiaou
     i
j
k
vow = 5, cnt = 0
m = {'a': 2, 'e': 1, 'i': 1, 'o': 1, 'u': 1, ...}

Now that we have all 5 vowels, we increment `cnt` by `k - j`, which represents the number of substrings ending at `i` and containing all vowels.

aeiaou
     i
j
k
vow = 5, cnt = 1
m = {'a': 2, 'e': 1, 'i': 1, 'o': 1, 'u': 1, ...}

Next, we move `k` to the right until the substring no longer contains all vowels.

aeiaou
     i
 j
  k
vow = 4, cnt = 2
m = {'a': 1, 'e': 1, 'i': 1, 'o': 1, 'u': 1, ...}

aeiaou
     i
  j
   k
vow = 4, cnt = 3
m = {'a': 1, 'e': 0, 'i': 1, 'o': 1, 'u': 1, ...}

We continue this process until `i` reaches the end of the word. The resulting `cnt` will be the number of substrings that contain all five vowels. Note that the counts in `m` represent the number of each vowel in the substring from `k` to `i`.

In each step, the pointers i, j, and k represent the current position in the word, the start of the current substring, and the start of the substring containing all vowels, respectively. The vow variable represents the number of unique vowels in the substring from k to i, and cnt represents the cumulative count of substrings containing all vowels. The dictionary m keeps track of the counts of each vowel in the substring from k to i.

The simplest useful example to illustrate the use of three pointers to solve a problem is the following:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
def find_max_consecutive_sum(nums):
    max_sum = 0
    curr_sum = 0
    start = 0
    end = 0

    for i in range(len(nums)):
        curr_sum += nums[i]
        if curr_sum < 0:
            curr_sum = 0
            start = i + 1
        else:
            if curr_sum > max_sum:
                max_sum = curr_sum
                end = i

    return max_sum, start, end

This code finds the maximum consecutive sum in the array nums. The code uses three pointers:

  • start: The index of the beginning of the current subarray.
  • end: The index of the end of the current subarray.
  • curr_sum: The sum of the elements in the current subarray.

The code works by first initializing the curr_sum variable to 0. The code then iterates through the array, updating the curr_sum variable and the start and end pointers as follows:

  • If the current element is positive, then the curr_sum variable is incremented.
  • If the current element is negative, then the curr_sum variable is reset to 0 and the start pointer is set to the index of the current element.
  • If the curr_sum variable is greater than the max_sum, then the max_sum variable is updated and the end pointer is set to the index of the current element.

The code terminates when the end of the array is reached. The code then returns the max_sum, start, and end variables.

This is a simple example of how three pointers can be used to solve a problem. The code is easy to understand and the use of three pointers makes the code efficient.

The simplest and most common use case for the three-pointer technique is probably the problem of finding a contiguous subarray within a one-dimensional array that has the largest sum, also known as the Kadane’s algorithm.

The algorithm maintains three pointers:

  1. start: This points to the start of the current subarray.
  2. end: This points to the end of the current subarray.
  3. search: This pointer traverses through the array, searching for potential candidates to start a new subarray.

Here’s a Python implementation of the algorithm:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
def maxSubArray(nums):
    max_sum = current_sum = nums[0]
    start = end = search = 0

    for i in range(1, len(nums)):
        if nums[i] > current_sum + nums[i]:
            current_sum = nums[i]
            search = i
        else:
            current_sum += nums[i]

        if current_sum > max_sum:
            max_sum = current_sum
            start = search
            end = i

    return max_sum, nums[start:end+1]

You can use the maxSubArray function to find the maximum sum of a subarray and the subarray itself:

1
2
nums = [-2, 1, -3, 4, -1, 2, 1, -5, 4]
print(maxSubArray(nums))  # Outputs: (6, [4, -1, 2, 1])

The maxSubArray function traverses the nums array. For each number, it decides whether to add the number to the current subarray or start a new subarray at this number. The decision is based on which option would give a larger sum. The start and end pointers are updated whenever a larger sum is found. The search pointer is updated whenever a new subarray starts. After traversing the entire array, the function returns the maximum sum found and the corresponding subarray.

The time complexity of this solution is O(n), where n is the size of the input string.

Here’s why:

  1. The solution uses a single pass over the string to construct the sliding window, which takes O(n) time.

  2. Within this pass, the operations performed are all constant time: checking and updating dictionary entries, comparing and updating variables, and incrementing pointers.

  3. Although there’s a nested while loop that contracts the window, it doesn’t significantly contribute to the time complexity. The reason is that each character in the string can be added and removed from the window at most once. Therefore, the total number of operations remains proportional to the size of the string.

So, despite the appearance of a nested loop, the time complexity remains linear because each character in the string is processed a constant number of times.

Breaking down a problem into smaller exercises is a great way to understand and tackle complex problems. Here are the steps to understand the problem:

  1. Understanding the problem: Given a string, find the number of substrings that contain all five vowels. A substring is a contiguous sequence of characters within a string. For this problem, the order of the vowels within the substring does not matter.

  2. Understanding the three-pointer technique: Familiarize yourself with the concept of the three-pointer or sliding window technique. This technique is often used for problems that involve arrays or strings where you need to find a contiguous subsequence that satisfies certain conditions.

  3. Tracking unique elements: In the given problem, we need to keep track of all unique vowels that have been seen so far in the current window. Understand how to use a dictionary or a set to keep track of unique elements in a sequence.

  4. Calculating the number of substrings ending at a specific position: For each position in the string, if all vowels are present in the substring ending at this position, you need to calculate the number of such substrings. Understand how to calculate this using the start position of the substring.

  5. Complete problem solution: Combine all the concepts and understand the complete solution. Go through the solution step by step, and understand how it uses a sliding window to find all substrings that contain all vowels.

Here are the different concepts used to solve the problem of counting vowel substrings of a string, and some exercises that you can do to practice each concept:

  1. Tracking the number of vowels in a substring. This can be done using a dictionary or a set. For example, you can create a dictionary where the keys are the vowels and the values are the number of times each vowel appears in the substring.

Exercise: Write a function that takes a string as input and returns a dictionary that tracks the number of vowels in the string.

  1. Iterating through the string and updating the vowel count. This can be done using a for loop. For example, you can iterate through the string and increment the vowel count for each vowel that appears in the string.

Exercise: Write a function that takes a string and a dictionary as input and updates the dictionary to track the number of vowels in the string.

  1. Determining if the substring has 5 vowels. This can be done by checking if the value of the vowel count is equal to 5.

Exercise: Write a function that takes a dictionary as input and returns True if the dictionary has a value of 5 for any of the vowels, and False otherwise.

  1. Counting the number of vowel substrings. This can be done by iterating through the string and counting the number of times the substring has 5 vowels.

Exercise: Write a function that takes a string as input and returns the number of vowel substrings in the string.

By practicing these exercises, you will gain a deeper understanding of the concepts used to solve the problem of counting vowel substrings of a string. You will also be able to write more efficient and elegant code to solve this problem.

Here is the Python3 code for each of the exercises:

Exercise 1:

1
2
3
4
5
6
7
def track_vowels(word):
    vowels = "aeiou"
    vowel_count = {}
    for char in word:
        if char in vowels:
            vowel_count[char] = vowel_count.get(char, 0) + 1
    return vowel_count

This code takes a string as input and returns a dictionary that tracks the number of vowels in the string. The dictionary is initialized with a value of 0 for each vowel. The code then iterates through the string and increments the vowel count for each vowel that appears in the string. The code finally returns the dictionary.

Exercise 2:

1
2
3
4
5
6
def update_vowel_count(word, vowel_count):
    vowels = "aeiou"
    for char in word:
        if char in vowels:
            vowel_count[char] += 1
    return vowel_count

This code takes a string and a dictionary as input and updates the dictionary to track the number of vowels in the string. The dictionary is initialized with the vowel count from the previous iteration. The code then iterates through the string and increments the vowel count for each vowel that appears in the string. The code finally returns the dictionary.

Exercise 3:

1
2
3
4
5
def has_five_vowels(vowel_count):
    for vowel in vowel_count:
        if vowel_count[vowel] == 5:
            return True
    return False

This code takes a dictionary as input and returns True if the dictionary has a value of 5 for any of the vowels, and False otherwise. The code iterates through the dictionary and checks if the value of any of the keys is equal to 5. If the code finds a key with a value of 5, then the code returns True. Otherwise, the code returns False.

Exercise 4:

1
2
3
4
5
6
7
8
def count_vowel_substrings(word):
    count = 0
    vowel_count = {}
    for i in range(len(word)):
        vowel_count = update_vowel_count(word[i:], vowel_count)
        if has_five_vowels(vowel_count):
            count += 1
    return count

This code takes a string as input and returns the number of vowel substrings in the string. The code first initializes the count variable to 0. The code then iterates through the string, updating the vowel_count dictionary and checking if the vowel_count dictionary has a value of 5 for any of the vowels. If the code finds a vowel_count dictionary with a value of 5, then the code increments the count variable. The code finally returns the count variable.

The size of the sliding window in this problem is dynamic.

In the sliding window technique, the window size can be either fixed or dynamic, depending on the problem at hand. In some problems, we maintain a window of a specific size and slide it across the array or string. In other problems, like this one, we dynamically adjust the size of the window based on certain conditions.

In this problem, the window represents a substring of the input string that contains all five vowels. The window size increases when we encounter a vowel that is not yet present in the window. When we have all five vowels in the window, we begin to contract the window from the left until we no longer have all vowels. This process continues as we traverse the string, and as a result, the window size changes dynamically.

The minimum size of the window is 5, which corresponds to a substring containing each of the five vowels exactly once. For example, in the string “aeiou”, the minimum window size is 5.

The maximum size of the window could potentially be the size of the entire input string, if the string is such that it contains all vowels but they are spread out over the entire string. For example, in the string “a…e…i…o…u” where “…” represents a sequence of non-vowel characters, the maximum window size would be the size of the entire string.

It’s important to note that the actual size of the window at any given point in the algorithm depends on the specific characters in the string and the requirements of the problem. The window size is dynamically adjusted to ensure that the window always contains all five vowels if possible.

First we need to construct the minimum possible window. We then check if we can expand this window. If not, we slide the window and keep checking. Is this correct?

Your understanding is mostly correct, but there’s a small adjustment to make. Instead of first constructing the minimum possible window, the approach actually starts by constructing a window that contains all five vowels, which could be larger than the minimum possible window of size 5.

Here’s the detailed process:

  1. Start by initializing an empty window. Expand this window to the right by adding characters from the string until all five vowels are included in the window. This step constructs the initial window, which includes all five vowels. Note that the size of this window could be larger than 5.

  2. Once all five vowels are in the window, attempt to contract the window from the left (by moving the left pointer k towards the right) until the window no longer contains all five vowels. The purpose of this step is to find the smallest window that ends at the current position and contains all five vowels.

  3. Calculate and add the number of substrings that end at the current position and contain all five vowels. This is done by subtracting the left pointer k from the right pointer i and adding the result to the total count.

  4. Move the right pointer i one step to the right and go back to step 1. If the character added to the window is a vowel, it might be possible to contract the window from the left.

  5. Repeat the above steps until the right pointer i has traversed the entire string.

So instead of expanding the window after constructing the minimum possible window, the algorithm actually contracts the window to its minimum possible size at each position. Then, it slides the window to the right and repeats the process.

Why does it construct the largest possible window instead of smallest possible window?

The algorithm starts by constructing a window that includes all five vowels, which could potentially be larger than the minimum possible window of size 5. The reason for this is that we need to ensure that the window contains all five vowels before we can start contracting it.

If we started with the smallest possible window of size 5, there’s no guarantee that this window would contain all five vowels. For example, if the input string starts with “aaaaa”, a window of size 5 would contain only one unique vowel, which is ‘a’.

Therefore, the algorithm first expands the window until it contains all five vowels. Once this condition is met, the algorithm then contracts the window from the left as much as possible while still maintaining the condition. This gives us the smallest possible window that ends at the current position and contains all five vowels.

This process is repeated as we slide the window through the string, ensuring at each position that we have the smallest possible window that contains all five vowels. This allows us to accurately count all substrings that contain all five vowels.

Here are 10 distinct problems that use similar underlying concepts:

  1. Minimum Size Subarray Sum: This problem requires finding the minimum size subarray with a sum that is greater or equal to a given value. It’s related because it involves finding a contiguous subarray that satisfies a condition and uses the sliding window concept.

  2. Longest Substring Without Repeating Characters: This problem involves finding the length of the longest substring without repeating characters. It’s similar because it requires managing a window of characters and tracking unique characters within that window.

  3. Sliding Window Maximum: In this problem, you need to find the maximum value in each sliding window of size k. This problem is related because it involves using the sliding window technique to track a property (maximum value) of the window.

  4. Permutation in String: The problem asks whether the first string’s permutation is the substring of the second string. The problem uses the sliding window technique and a character count map, which are key components in our original problem.

  5. Longest Substring with At Most Two Distinct Characters: This problem involves finding the longest substring with at most two distinct characters. It’s related because it requires managing a window of characters and tracking the number of distinct characters within that window.

  6. Find All Anagrams in a String: This problem requires finding all the start indices of a string p’s anagrams in a string s. It’s similar because it uses a sliding window to track characters and their counts.

  7. Subarrays with K Different Integers: This problem involves finding the number of subarrays with exactly K different integers. It’s related because it requires tracking unique elements within a window in an array.

  8. Max Consecutive Ones III: This problem requires finding the maximum number of consecutive 1s that can be obtained by flipping at most K zeros. It’s related because it involves using the sliding window technique to maintain a condition within the window.

  9. Longest Repeating Character Replacement: This problem involves finding the length of the longest substring containing the same letter you can get after performing k operations. It uses a sliding window and character counting, similar to the original problem.

  10. Fruit Into Baskets: This problem involves finding the maximum number of fruits you can collect. It’s related because it requires managing a window of elements and tracking the number of unique elements within that window.

Each of these problems involves similar concepts of managing a window of elements in an array or string, and tracking certain properties (like the sum, maximum value, or number of unique elements) within that window.

Here are 10 distinct problems that use similar underlying concepts to the problem of counting vowel substrings of a string:

  1. Maximum Subarray. This problem asks you to find the maximum sum of a contiguous subarray in an array. This problem is similar to the problem of counting vowel substrings because it also involves tracking the sum of a sequence of elements.
  2. Longest Palindromic Substring. This problem asks you to find the longest palindromic substring in a string. This problem is similar to the problem of counting vowel substrings because it also involves tracking the length of a sequence of elements.
  3. Minimum Window Substring. This problem asks you to find the smallest substring of a string that contains all of the characters in a given set. This problem is similar to the problem of counting vowel substrings because it also involves tracking the presence of characters in a substring.
  4. Distinct Subsequences. This problem asks you to find the number of distinct subsequences of a string. This problem is similar to the problem of counting vowel substrings because it also involves counting the number of sequences of elements that can be formed from a string.
  5. Longest Common Subsequence. This problem asks you to find the longest substring that is common to two strings. This problem is similar to the problem of counting vowel substrings because it also involves tracking the length of a sequence of elements that is common to two strings.
  6. K-diff Pairs in an Array. This problem asks you to find the number of pairs of elements in an array that have a difference of K. This problem is similar to the problem of counting vowel substrings because it also involves tracking the presence of pairs of elements in an array.
  7. Subarrays with Product Less than K. This problem asks you to find the number of subarrays in an array whose product is less than K. This problem is similar to the problem of counting vowel substrings because it also involves tracking the product of elements in a substring.
  8. Permutations with Duplicates. This problem asks you to find the number of permutations of a string where the characters can be repeated. This problem is similar to the problem of counting vowel substrings because it also involves counting the number of sequences of elements that can be formed from a string.
  9. Counting Inversions. This problem asks you to find the number of inversions in an array. An inversion is a pair of elements in an array where the first element is greater than the second element. This problem is similar to the problem of counting vowel substrings because it also involves tracking the order of elements in an array.
  10. Counting Triangles. This problem asks you to find the number of triangles that can be formed from a set of points. This problem is similar to the problem of counting vowel substrings because it also involves counting the number of sequences of elements that can be formed from a set.

Why do we not iterate k so that [k,i] is the smallest window containing all the vowels and not [k-1,i]?

In the given algorithm, we maintain a sliding window from index k to i which includes all the vowels. We increment k until the window doesn’t contain all the vowels. At that point, k is positioned at the first index from the start which, when removed, would lead to the window missing a vowel. This is why the smallest valid window ends up being [k-1, i] and not [k, i].

This is the essence of the sliding window technique: we extend the window to the right as far as possible and then move the left boundary to the right until the window is valid again.

Therefore, we don’t stop at [k, i] because we know that the window [k-1, i] still includes all the vowels, and hence, it’s also a valid window that should be counted. That’s why we count all the windows from [j, i] to [k-1, i], where j is the beginning of the current window and k is the index where the window becomes invalid.