Count Beautiful Substrings II
HardPrompt
You are given a string s and a positive integer k.
Let vowels and consonants be the number of vowels and consonants in a string.
A string is beautiful if:
vowels == consonants.(vowels * consonants) % k == 0, in other terms the multiplication ofvowelsandconsonantsis divisible byk.
Return the number of non-empty beautiful substrings in the given string s.
A substring is a contiguous sequence of characters in a string.
Vowel letters in English are 'a', 'e', 'i', 'o', and 'u'.
Consonant letters in English are every letter except vowels.
Example 1:
Input: s = "baeyh", k = 2
Output: 2
Explanation: There are 2 beautiful substrings in the given string.
- Substring "baeyh", vowels = 2 (["a",e"]), consonants = 2 (["y","h"]).
You can see that string "aeyh" is beautiful as vowels == consonants and vowels * consonants % k == 0.
- Substring "baeyh", vowels = 2 (["a",e"]), consonants = 2 (["b","y"]).
You can see that string "baey" is beautiful as vowels == consonants and vowels * consonants % k == 0.
It can be shown that there are only 2 beautiful substrings in the given string.Example 2:
Input: s = "abba", k = 1
Output: 3
Explanation: There are 3 beautiful substrings in the given string.
- Substring "abba", vowels = 1 (["a"]), consonants = 1 (["b"]).
- Substring "abba", vowels = 1 (["a"]), consonants = 1 (["b"]).
- Substring "abba", vowels = 2 (["a","a"]), consonants = 2 (["b","b"]).
It can be shown that there are only 3 beautiful substrings in the given string.Example 3:
Input: s = "bcdf", k = 1
Output: 0
Explanation: There are no beautiful substrings in the given string.
Constraints:
1 <= s.length <= 5 * 1041 <= k <= 1000sconsists of only English lowercase letters.
Approaches
2 approaches with complexity analysis and trade-offs.
This approach involves checking every possible substring of the given string s. For each substring, we count the vowels and consonants and verify if it meets the two conditions for being 'beautiful'. To avoid a TLE (Time Limit Exceeded) error from a naive O(N^3) implementation, we optimize the counting process.
Algorithm
- Initialize a counter
ansto 0. - Iterate through the string with a starting index
ifrom 0 ton-1. - For each
i, initializevowels = 0andconsonants = 0. - Start a nested loop for the ending index
jfromiton-1.- Update
vowelsandconsonantsbased on the characters[j]. - Check if
vowels > 0andvowels == consonants. - If true, check if
(vowels * vowels) % k == 0. - If both conditions are met, increment
ans.
- Update
- Return
ans.
Walkthrough
We can iterate through all possible starting positions i of a substring. For each i, we start another loop for the ending position j from i to the end of the string. As we extend the substring by one character at j, we maintain a running count of vowels and consonants. This allows us to check the conditions for the substring s[i...j] in O(1) time within the inner loop.
The two conditions for a beautiful substring are:
vowels == consonants(vowels * consonants) % k == 0
If both are true for the current substring, we increment our result counter.
class Solution { private boolean isVowel(char c) { return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u'; } public long beautifulSubstrings(String s, int k) { long count = 0; int n = s.length(); for (int i = 0; i < n; i++) { int vowels = 0; int consonants = 0; for (int j = i; j < n; j++) { if (isVowel(s.charAt(j))) { vowels++; } else { consonants++; } if (vowels > 0 && vowels == consonants) { if ((long)vowels * vowels % k == 0) { count++; } } } } return count; }}This approach is straightforward but its quadratic time complexity makes it too slow for the given constraints.
Complexity
Time
O(N^2), where N is the length of the string `s`. The two nested loops iterate through all `O(N^2)` substrings, and the check for each is O(1).
Space
O(1), as we only use a few variables to store counts, regardless of the input size.
Trade-offs
Pros
Simple to understand and implement.
Low memory usage.
Cons
Inefficient for large inputs, leading to Time Limit Exceeded (TLE).
This optimal solution leverages number theory and prefix sums (differences) to count beautiful substrings in linear time. By transforming the two conditions into properties of prefix states, we can use a hash map to efficiently find matching substrings without iterating through all of them.
Algorithm
- Calculate a
periodbased on4*k. This is done by finding the prime factorization of4*kand constructing a new number where each primepwith exponentais replaced bypwith exponentceil(a/2). - Initialize
ans = 0,diff = 0. - Initialize a hash map
countsto store frequencies of(diff, index % period)states. - Put the initial state for an index
p = -1into the map:diff = 0,rem = (-1 % period + period) % period. The count for state(0, rem)is 1. - Iterate through the string with index
jfrom 0 ton-1:- Update
diffbased ons[j](+1 for vowel, -1 for consonant). - Calculate
rem = j % period. - Form the state key from
(diff, rem). - Get the count of this state from the
countsmap and add it toans. - Increment the count for the current state in the map.
- Update
- Return
ans.
Walkthrough
The problem's conditions can be rephrased to enable a more efficient counting method. Let's analyze a substring s[i...j].
-
vowels == consonants: We can model this by assigning+1to vowels and-1to consonants. A substring satisfies this condition if the sum of these values over its characters is 0. Using a prefix difference arraydiff, wherediff[x]is the net sum for the prefixs[0...x], this condition becomesdiff[j] == diff[i-1]. -
(vowels * consonants) % k == 0: Sincevowels == consonants, the substring lengthL = j - i + 1must be even, withvowels = L / 2. The condition becomes(L/2)^2 % k == 0, which simplifies toL^2 % (4*k) == 0. This number theory condition holds if and only ifLis a multiple of a specific number, which we'll callperiod. Thisperiodis derived from the prime factorization of4*k. The conditionL % period == 0is equivalent to(j - (i-1)) % period == 0, which meansj % period == (i-1) % period.
Combining these, we need to find pairs of indices (p, j) (where p = i-1) that satisfy diff[j] == diff[p] and j % period == p % period. We can iterate through the string, tracking the state (diff, index % period). A hash map stores the frequencies of these states, allowing us to find the number of valid starting points for each ending point j in O(1) time.
import java.util.HashMap;import java.util.Map; class Solution { private boolean isVowel(char c) { return c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u'; } private int calculatePeriod(int k) { long num = 4L * k; int period = 1; for (long i = 2; i * i <= num; i++) { if (num % i == 0) { int count = 0; while (num % i == 0) { count++; num /= i; } for (int j = 0; j < (count + 1) / 2; j++) { period *= i; } } } if (num > 1) { period *= num; } return period; } public long beautifulSubstrings(String s, int k) { int period = calculatePeriod(k); int n = s.length(); Map<String, Integer> counts = new HashMap<>(); int initialRem = (-1 % period + period) % period; counts.put("0:" + initialRem, 1); long ans = 0; int diff = 0; for (int j = 0; j < n; j++) { if (isVowel(s.charAt(j))) { diff++; } else { diff--; } int rem = j % period; String key = diff + ":" + rem; int prevCount = counts.getOrDefault(key, 0); ans += prevCount; counts.put(key, prevCount + 1); } return ans; }}Complexity
Time
O(N + sqrt(K)), where N is the string length. Calculating the `period` takes `O(sqrt(K))`. The main loop is `O(N)` with `O(1)` average time for hash map operations.
Space
O(N). The hash map can store up to N+1 distinct states in the worst case.
Trade-offs
Pros
Highly efficient with linear time complexity.
Scales well for large inputs.
Cons
The logic is complex and relies on number theory, making it less intuitive.
Requires careful implementation of state representation and modular arithmetic.
Video walkthrough
Newsletter
One sharp idea, every week
System design and interview prep — short enough to finish.
No spam. Unsubscribe anytime.
Practice
Same difficulty — related problems to reinforce the pattern.