{x}
blog image

Number of Wonderful Substrings

A wonderful string is a string where at most one letter appears an odd number of times.

  • For example, "ccjjc" and "abab" are wonderful, but "ab" is not.

Given a string word that consists of the first ten lowercase English letters ('a' through 'j'), return the number of wonderful non-empty substrings in word. If the same substring appears multiple times in word, then count each occurrence separately.

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

 

Example 1:

Input: word = "aba"
Output: 4
Explanation: The four wonderful substrings are underlined below:
- "aba" -> "a"
- "aba" -> "b"
- "aba" -> "a"
- "aba" -> "aba"

Example 2:

Input: word = "aabb"
Output: 9
Explanation: The nine wonderful substrings are underlined below:
- "aabb" -> "a"
- "aabb" -> "aa"
- "aabb" -> "aab"
- "aabb" -> "aabb"
- "aabb" -> "a"
- "aabb" -> "abb"
- "aabb" -> "b"
- "aabb" -> "bb"
- "aabb" -> "b"

Example 3:

Input: word = "he"
Output: 2
Explanation: The two wonderful substrings are underlined below:
- "he" -> "h"
- "he" -> "e"

 

Constraints:

  • 1 <= word.length <= 105
  • word consists of lowercase English letters from 'a' to 'j'.

Solution Explanation:

This problem leverages bit manipulation and prefix sums to efficiently count wonderful substrings. A "wonderful" substring is defined as a substring where at most one character appears an odd number of times.

Core Idea:

We use a 10-bit integer (st) to represent the parity (even or odd) of the counts of each of the ten characters ('a' to 'j'). Each bit corresponds to a character: if the bit is set, the character count is odd; otherwise, it's even.

We iterate through the string, updating st as we encounter each character. For each prefix of the string, we check how many previously seen prefixes have a bitmask that differs from the current prefix's bitmask by at most one bit (meaning at most one character has a different parity). This difference indicates a wonderful substring.

Algorithm:

  1. Initialization: Create a cnt array (hash map or array) to store the counts of each bitmask encountered. Initialize cnt[0] to 1 because the empty substring has all even counts. st (state) is initialized to 0. ans (answer) is initialized to 0.

  2. Iteration: Iterate through the characters of the word.

  3. Update State: For each character, update st by XORing it with 1 << (c - 'a'). This flips the bit corresponding to the character, reflecting the change in parity.

  4. Count Wonderful Substrings:

    • Add cnt[st] to ans. This counts substrings ending at the current position with the same parity as the current prefix.
    • Add cnt[st ^ (1 << i)] for i from 0 to 9 to ans. This counts substrings where exactly one character has different parity compared to the current prefix.
  5. Update Count: Increment cnt[st] to record the occurrence of the current bitmask.

  6. Return: Return ans, the total count of wonderful substrings.

Time Complexity: O(N), where N is the length of the word. We iterate through the string once. The inner loop iterates 10 times in each iteration of the outer loop, contributing a constant factor.

Space Complexity: O(210) = O(1), because the cnt array has a fixed size of 1024 (210). The space used is constant, regardless of the input size.

Example Walkthrough (word = "aba"):

  • Initially, cnt[0] = 1, st = 0, ans = 0.

  • 'a': st becomes 1 << 0 = 1. ans += cnt[1] (0) + cnt[0] (1) + ... = 1. cnt[1] = 1.

  • 'b': st becomes 1 ^ (1 << 1) = 3. ans += cnt[3] (0) + cnt[2] (0) + cnt[1] (1) + cnt[0] (1) = 2. cnt[3] = 1.

  • 'a': st becomes 3 ^ (1 << 0) = 2. ans += cnt[2] (0) + cnt[3] (1) + cnt[1] (1) + cnt[0] (1) = 4. cnt[2] = 1.

  • ans (4) is returned.

The code provided in various languages implements this algorithm efficiently. The use of bit manipulation significantly optimizes the solution's performance.