2949. Count Beautiful Substrings II
Problem Description
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 ofvowels
andconsonants
is 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.
Intuition
To solve the problem of finding beautiful substrings, a strategic approach involves leveraging prefix sums and a hash table. Here's the reasoning behind the solution:
-
Counting Vowels and Consonants:
- By assigning a specific value to each character in the string, it's possible to distinguish between vowels and consonants.
- Using a bitmask (like
AEIOU_MASK
), each character ofs
can be quickly checked if it's a vowel.
-
Prefix Sum Technique:
- As you iterate through the string, compute a running sum that increases or decreases based on whether the current character is a vowel or consonant.
- A key insight is that when the prefix sum of vowels and consonants at two different indices is the same, the substring between these two indices has equal numbers of vowels and consonants.
-
Hash Map Utility:
- Use a hash map to keep track of the number of times a particular prefix sum (combined with the modulus of its index) occurs.
- For each character, compute a key based on the prefix sum and its index modulus. Check how many times this key has occurred previously.
- Each previous occurrence of the same key corresponds to a valid beautiful substring.
-
Perfect Square Optimization via
pSqrt
Function:- The solution exploits properties of perfect squares to limit the search space effectively. By tuning the prefix sums with certain properties, we ensure that our calculations stay efficient and the substrings checked maintain the required criteria.
By applying these principles, the solution efficiently identifies all beautiful substrings using computationally effective operations.
Learn more about Math and Prefix Sum patterns.
Solution Approach
The solution centers around the [Prefix Sum](/problems/subarray_sum)
technique combined with a Hash Table
to efficiently track and count the desired substrings.
Key Components
-
AEIOU Mask:
- The
AEIOU_MASK
is a bitmask representing vowels. For each character in the strings
, this mask helps determine if the character is a vowel or consonant.
- The
-
Prefix Sum Calculation:
- As we iterate over the string
s
, we calculate a running sumsum
that increases by2
for vowels and decreases by1
for consonants. - The idea is to use even and odd values to balance the counts of vowels and consonants.
- As we iterate over the string
-
Hash Table (Map):
- A
Map
is used to keep track of the number of occurrences of each(index % l, sum)
pair, wherel
is derived using thepSqrt
function. - This
(index % l, sum)
pair effectively represents unique conditions for beautiful strings in terms of position and vowel/consonant balance.
- A
-
Counting Beautiful Substrings:
- As each character in the string
s
is processed, generate a key derived from the current index and sum. - If this key exists in the
Map
, it indicates the presence of previous indices with the same sum, which are potential starting points for a beautiful substring. - Increment the count of beautiful substrings (
ans
) based on the current count of the key in theMap
.
- As each character in the string
-
Perfect Square Reduction:
- The function
pSqrt(k * 4)
provides a reduced lengthl
for prefix balancing. This calculation leverages mathematical properties associated with perfect squares to keep the process efficient and limit unnecessary checks.
- The function
Code Overview
Here's a breakdown of the code workflow:
function beautifulSubstrings(s: string, k: number): number {
const l = pSqrt(k * 4); // Calculate a reduced length for efficient modulo operations.
const n = s.length;
let sum = n; // Initialize the sum using the length of the string.
let ans = 0; // Initialize answer count.
const counter = new Map(); // Map for tracking (index % l, sum) occurrences.
counter.set(((l - 1) << 17) | sum, 1); // Initialize the map with a starting key.
for (let i = 0; i < n; i++) {
const char = s[i];
const bit = (AEIOU_MASK >> (char.charCodeAt(0) - 'a'.charCodeAt(0))) & 1; // Check if character is a vowel.
sum += bit * 2 - 1; // Adjust sum based on whether the character is a vowel or consonant.
const key = (i % l << 17) | sum; // Create a unique key for (index % l, sum).
ans += counter.get(key) || 0; // Increment answer with count of existing key.
counter.set(key, (counter.get(key) ?? 0) + 1); // Update map with incremented count for the key.
}
return ans; // Return the total count of beautiful substrings.
}
const AEIOU_MASK = 1065233; // Binary mask for vowels.
function pSqrt(n: number) {
let res = 1;
for (let i = 2; i * i <= n; i++) {
let i2 = i * i;
while (n % i2 == 0) {
res *= i;
n /= i2;
}
if (n % i == 0) {
res *= i;
n /= i;
}
}
if (n > 1) {
res *= n;
}
return res; // Returns the square root of n, adjusted for perfect square factors.
}
This approach efficiently manages the substring checks using arithmetic properties and hash mapping, ensuring both speed and accuracy.
Ready to land your dream job?
Unlock your dream job with a 2-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Consider the string s = "abbaaa"
and k = 2
.
-
Initial Setup:
l
is calculated usingpSqrt(k * 4)
. Fork = 2
, it computes topSqrt(8)
, which simplifies to2
. This means we use a modulus base of2
for our indexing.- Initialize
sum = n = 6
(size ofs
),ans = 0
, and aMap
to keep track of seen keys.
-
Processing Characters:
-
Character 0 (
a
):a
is a vowel, sobit = 1
.- Update
sum
usingsum += bit * 2 - 1 = 6 + 2 - 1 = 7
. - Calculate key with
key = (0 % 2 << 17) | 7 = 7
. - Check the
Map
for key7
and incrementans
bycounter[7]
which is0
initially. - Update the
Map
:counter[7] = 1
.
-
Character 1 (
b
):b
is a consonant, sobit = 0
.- Update
sum
usingsum += bit * 2 - 1 = 7 - 1 = 6
. - Calculate key with
key = (1 % 2 << 17) | 6 = 131078
. - Check the
Map
for key131078
and incrementans
bycounter[131078]
which is0
initially. - Update the
Map
:counter[131078] = 1
.
-
Character 2 (
b
):b
is a consonant, sobit = 0
.- Update
sum
usingsum += bit * 2 - 1 = 6 - 1 = 5
. - Calculate key with
key = (2 % 2 << 17) | 5 = 5
. - Check the
Map
for key5
and incrementans
bycounter[5]
which is0
initially. - Update the
Map
:counter[5] = 1
.
-
Character 3 (
a
):a
is a vowel, sobit = 1
.- Update
sum
usingsum += bit * 2 - 1 = 5 + 2 - 1 = 6
. - Calculate key with
key = (3 % 2 << 17) | 6 = 131078
. - Check the
Map
for key131078
and incrementans
bycounter[131078]
which is1
. - Update
ans
:ans = 1
. - Update the
Map
:counter[131078] = 2
.
-
Character 4 (
a
):a
is a vowel, sobit = 1
.- Update
sum
usingsum += bit * 2 - 1 = 6 + 2 - 1 = 7
. - Calculate key with
key = (4 % 2 << 17) | 7 = 7
. - Check the
Map
for key7
and incrementans
bycounter[7]
which is1
. - Update
ans
:ans = 2
. - Update the
Map
:counter[7] = 2
.
-
Character 5 (
a
):a
is a vowel, sobit = 1
.- Update
sum
usingsum += bit * 2 - 1 = 7 + 2 - 1 = 8
. - Calculate key with
key = (5 % 2 << 17) | 8 = 131080
. - Check the
Map
for key131080
and incrementans
bycounter[131080]
which is0
. - Update the
Map
:counter[131080] = 1
.
-
-
Return the Result:
- The total number of beautiful substrings found is
ans = 2
.
- The total number of beautiful substrings found is
In this walk-through, we derived 2
beautiful substrings from the existing setup. This explanation demonstrates how the algorithm efficiently scopes through each character, updates its internal state, and counts substrings that meet the specified criteria using prefix sums and hash mapping.
Solution Implementation
1def beautifulSubstrings(s: str, k: int) -> int:
2 l = pSqrt(k * 4) # Calculate the modified period length factor
3 n = len(s) # Get the length of the input string
4 sum_val = n # Initialize sum_val with the length of the string
5 answer = 0 # Counter for beautiful substrings
6 counter = {} # Dictionary to track occurrences of states
7 counter[((l - 1) << 17) | sum_val] = 1 # Initialize the dictionary with the start state
8
9 # AEIOU_MASK is a bitmask where a bit is set for each vowel (a, e, i, o, u)
10 AEIOU_MASK = 1065233
11
12 for i in range(n):
13 char = s[i] # Current character of the string
14 # Calculate the bit value: 1 if char is a vowel, else 0
15 bit = (AEIOU_MASK >> (ord(char) - ord('a'))) & 1
16 sum_val += bit * 2 - 1 # Adjust sum_val: 1 for vowels, -1 for non-vowels
17
18 # Calculate the key based on the current index and sum_val
19 key = ((i % l) << 17) | sum_val
20
21 # Increment the answer by the number of times this state has been encountered
22 answer += counter.get(key, 0)
23
24 # Update the counter dictionary with the current key's occurrence
25 counter[key] = counter.get(key, 0) + 1
26
27 return answer # Return the total count of beautiful substrings
28
29def pSqrt(n: int) -> int:
30 res = 1 # Initialize the result as 1
31 i = 2
32
33 while i * i <= n:
34 i2 = i * i # Square of the current factor
35
36 # Divide out all factors of i^2 as much as possible
37 while n % i2 == 0:
38 res *= i # Multiply result by i
39 n //= i2 # Reduce n by factor of i^2
40
41 # Divide out a single factor of i if n is still divisible by i
42 if n % i == 0:
43 res *= i # Multiply result by i
44 n //= i # Reduce n by factor of i
45
46 i += 1
47
48 # If n is greater than 1, multiply the result by n (handling remaining prime factor)
49 if n > 1:
50 res *= n
51
52 return res # Return the final calculated product
53
1import java.util.HashMap;
2import java.util.Map;
3
4public class BeautifulSubstrings {
5
6 // AEIOU_MASK is a bitmask where a bit is set for each vowel (a, e, i, o, u)
7 private static final int AEIOU_MASK = 1065233;
8
9 public static int beautifulSubstrings(String s, int k) {
10 int l = pSqrt(k * 4); // Calculate the modified period length factor
11 int n = s.length(); // Get the length of the input string
12 int sum = n; // Initialize sum with the length of the string
13 int ans = 0; // Counter for beautiful substrings
14 Map<Integer, Integer> counter = new HashMap<>(); // Map to track occurrences of states
15 counter.put(((l - 1) << 17) | sum, 1); // Initialize the map with the start state
16
17 for (int i = 0; i < n; i++) {
18 char c = s.charAt(i); // Current character of the string
19 // Calculate the bit value: 1 if char is a vowel (in AEIOU_MASK), else 0
20 int bit = (AEIOU_MASK >> (c - 'a')) & 1;
21 sum += bit * 2 - 1; // Adjust sum: 1 for vowels, -1 for non-vowels
22
23 // Calculate the key based on the current index and sum
24 int key = ((i % l) << 17) | sum;
25
26 // Increment the answer by the number of times this state has been encountered
27 ans += counter.getOrDefault(key, 0);
28
29 // Update the counter map with the current key's occurrence
30 counter.put(key, counter.getOrDefault(key, 0) + 1);
31 }
32 return ans; // Return the total count of beautiful substrings
33 }
34
35 /**
36 * Calculates the largest factor of n that is a perfect square.
37 *
38 * @param n The number to be factored.
39 * @return The product of prime factors raised to an appropriate power.
40 */
41 public static int pSqrt(int n) {
42 int res = 1; // Initialize the result as 1
43 for (int i = 2; i * i <= n; i++) {
44 int i2 = i * i; // Square of the current factor
45
46 // Divide out all factors of i^2 as much as possible
47 while (n % i2 == 0) {
48 res *= i; // Multiply result by i
49 n /= i2; // Reduce n by factor of i^2
50 }
51
52 // Divide out a single factor of i if n is still divisible by i
53 if (n % i == 0) {
54 res *= i; // Multiply result by i
55 n /= i; // Reduce n by factor of i
56 }
57 }
58
59 // If n is greater than 1, multiply the result by n (handling remaining prime factor)
60 if (n > 1) {
61 res *= n;
62 }
63
64 return res; // Return the final calculated product
65 }
66}
67
1#include <iostream>
2#include <unordered_map>
3#include <string>
4
5using namespace std;
6
7const int AEIOU_MASK = 1065233; // Bitmask where bits for vowels (a, e, i, o, u) are set
8
9/**
10 * Calculates the largest factor of n that is a perfect square.
11 *
12 * @param n The number to be factored.
13 * @returns The product of prime factors raised to an appropriate power.
14 */
15int pSqrt(int n) {
16 int res = 1; // Initialize result as 1
17 for (int i = 2; i * i <= n; i++) {
18 int i2 = i * i; // Square of current factor
19
20 // Divide out all factors of i^2 as much as possible
21 while (n % i2 == 0) {
22 res *= i; // Multiply result by i
23 n /= i2; // Reduce n by factor of i^2
24 }
25
26 // Divide out a single factor of i if n is still divisible by i
27 if (n % i == 0) {
28 res *= i; // Multiply result by i
29 n /= i; // Reduce n by a factor of i
30 }
31 }
32
33 // If n is greater than 1, multiply the result by n (handling remaining prime factor)
34 if (n > 1) {
35 res *= n;
36 }
37
38 return res; // Return the final calculated product
39}
40
41/**
42 * Counts the number of beautiful substrings in a given string.
43 *
44 * @param s The input string.
45 * @param k A parameter to adjust period length.
46 * @returns The total count of beautiful substrings.
47 */
48int beautifulSubstrings(const string& s, int k) {
49 int l = pSqrt(k * 4); // Calculate the modified period length factor
50 int n = s.length(); // Get the length of the input string
51 int sum = n; // Initialize sum with the length of the string
52 int ans = 0; // Counter for beautiful substrings
53 unordered_map<int, int> counter; // Map to track occurrences of states
54 counter[((l - 1) << 17) | sum] = 1; // Initialize the map with the start state
55
56 for (int i = 0; i < n; i++) {
57 char char = s[i]; // Current character of the string
58 // Calculate the bit value: 1 if char is a vowel (in AEIOU_MASK), else 0
59 int bit = (AEIOU_MASK >> (char - 'a')) & 1;
60 sum += bit * 2 - 1; // Adjust sum: 1 for vowels, -1 for non-vowels
61
62 // Calculate the key based on the current index and sum
63 int key = ((i % l) << 17) | sum;
64
65 // Increment the answer by the number of times this state has been encountered
66 ans += counter[key];
67
68 // Update the counter map with the current key's occurrence
69 counter[key]++;
70 }
71 return ans; // Return the total count of beautiful substrings
72}
73
74int main() {
75 // Example usage
76 string s = "aeiou";
77 int k = 3;
78 cout << "Beautiful Substrings: " << beautifulSubstrings(s, k) << endl;
79 return 0;
80}
81
1function beautifulSubstrings(s: string, k: number): number {
2 const l = pSqrt(k * 4); // Calculate the modified period length factor
3 const n = s.length; // Get the length of the input string
4 let sum = n; // Initialize sum with the length of the string
5 let ans = 0; // Counter for beautiful substrings
6 const counter = new Map<number, number>(); // Map to track occurrences of states
7 counter.set(((l - 1) << 17) | sum, 1); // Initialize the map with the start state
8
9 for (let i = 0; i < n; i++) {
10 const char = s[i]; // Current character of the string
11 // Calculate the bit value: 1 if char is a vowel (in AEIOU_MASK), else 0
12 const bit = (AEIOU_MASK >> (char.charCodeAt(0) - 'a'.charCodeAt(0))) & 1;
13 sum += bit * 2 - 1; // Adjust sum: 1 for vowels, -1 for non-vowels
14
15 // Calculate the key based on the current index and sum
16 const key = ((i % l) << 17) | sum;
17
18 // Increment the answer by the number of times this state has been encountered
19 ans += counter.get(key) || 0;
20
21 // Update the counter map with the current key's occurrence
22 counter.set(key, (counter.get(key) ?? 0) + 1);
23 }
24 return ans; // Return the total count of beautiful substrings
25}
26
27// AEIOU_MASK is a bitmask where a bit is set for each vowel (a, e, i, o, u)
28const AEIOU_MASK = 1065233;
29
30/**
31 * Calculates the largest factor of n that is a perfect square.
32 *
33 * @param n The number to be factored.
34 * @returns The product of prime factors raised to an appropriate power.
35 */
36function pSqrt(n: number): number {
37 let res = 1; // Initialize the result as 1
38 for (let i = 2; i * i <= n; i++) {
39 let i2 = i * i; // Square of the current factor
40
41 // Divide out all factors of i^2 as much as possible
42 while (n % i2 === 0) {
43 res *= i; // Multiply result by i
44 n /= i2; // Reduce n by factor of i^2
45 }
46
47 // Divide out a single factor of i if n is still divisible by i
48 if (n % i === 0) {
49 res *= i; // Multiply result by i
50 n /= i; // Reduce n by factor of i
51 }
52 }
53
54 // If n is greater than 1, multiply the result by n (handling remaining prime factor)
55 if (n > 1) {
56 res *= n;
57 }
58
59 return res; // Return the final calculated product
60}
61
Time and Space Complexity
The time complexity of the beautifulSubstrings
function is O(n)
, where n
is the length of the input string s
. This is because the function iterates over the string with a single loop running n
times. Each operation inside the loop, including accessing and updating the Map
, takes constant time on average.
The pSqrt
function runs in O(sqrt(k))
time complexity, which is the time taken to find the perfect square root decomposition using trial division up to the square root of k
.
Overall, the time complexity of the combined code is O(n + sqrt(k))
.
The space complexity is O(l + n)
, where l
is the result of pSqrt(k * 4)
. This includes the space used by the counter
Map which can store up to O(n)
entries, each representing a unique state combination of (i % l << 17) | sum
.
Learn more about how to find time and space complexity quickly.
Which of these properties could exist for a graph but not a tree?
Recommended Readings
Math for Technical Interviews How much math do I need to know for technical interviews The short answer is about high school level math Computer science is often associated with math and some universities even place their computer science department under the math faculty However the reality is that you
Prefix Sum The prefix sum is an incredibly powerful and straightforward technique Its primary goal is to allow for constant time range sum queries on an array What is Prefix Sum The prefix sum of an array at index i is the sum of all numbers from index 0 to i By
Coding Interview Patterns Your Personal Dijkstra's Algorithm to Landing Your Dream Job The goal of AlgoMonster is to help you get a job in the shortest amount of time possible in a data driven way We compiled datasets of tech interview problems and broke them down by patterns This way
Want a Structured Path to Master System Design Too? Don’t Miss This!