Facebook Pixel

2514. Count Anagrams

HardHash TableMathStringCombinatoricsCounting
Leetcode Link

Problem Description

You have a string s that contains one or more words separated by single spaces.

A string t is considered an anagram of string s if each word in t is a rearrangement (permutation) of the corresponding word in s at the same position.

For example:

  • "acb dfe" is an anagram of "abc def" because:
    • The first word "acb" is a permutation of "abc"
    • The second word "dfe" is a permutation of "def"
  • "def cab" is NOT an anagram of "abc def" because the words are in different positions
  • "adc bef" is NOT an anagram of "abc def" because "adc" is not a permutation of "abc"

Your task is to count how many distinct anagrams the string s can form. Since the answer can be very large, return it modulo 10^9 + 7.

The solution calculates this by:

  1. For each word in s, determining how many different ways its letters can be arranged (permutations)
  2. Multiplying the permutation counts of all words together to get the total number of distinct anagrams

The mathematical approach uses the formula for permutations with repetitions: for a word of length n with repeated characters, the number of distinct permutations is n! / (c1! × c2! × ... × ck!) where c1, c2, ..., ck are the frequencies of each unique character.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

To find the total number of distinct anagrams, we need to think about what makes anagrams distinct. Since each word must maintain its position (the first word stays first, second stays second, etc.), we can treat each word independently and multiply their possibilities together.

For a single word, the number of distinct arrangements depends on repeated characters. If a word has all unique characters like "abc", we have 3! = 6 arrangements. But if a word is "aab", we have fewer distinct arrangements because swapping the two 'a's doesn't create a new arrangement. The formula becomes 3! / 2! = 3 (the arrangements are "aab", "aba", "baa").

This leads us to the general formula: for a word of length n with character frequencies f1, f2, ..., fk, the number of distinct permutations is: n! / (f1! × f2! × ... × fk!)

The clever part of the solution is how it computes this efficiently. Instead of calculating factorials separately and then dividing, it builds the result incrementally:

  • As we process each character at position i, we multiply our answer by i (building up the numerator n!)
  • We track the frequency of each character and multiply by that frequency (building up the denominator)
  • At the end, we use modular inverse to perform the division under modulo

The modular inverse is necessary because we're working with modulo 10^9 + 7, and direct division doesn't work properly with modular arithmetic. Using Fermat's Little Theorem, a^(-1) ≡ a^(p-2) (mod p) when p is prime, which allows us to convert division into multiplication.

This approach processes all words simultaneously, accumulating the total count of anagrams by maintaining the running product of possibilities for each word position.

Learn more about Math and Combinatorics patterns.

Solution Approach

The implementation uses a mathematical approach to count permutations with repetitions, processing each word in the string sequentially.

Key Variables:

  • ans: Accumulates the product of all factorial values (numerators)
  • mul: Accumulates the product of all character frequency factorials (denominators)
  • mod: The modulo value 10^9 + 7 to prevent overflow

Step-by-step Implementation:

  1. Initialize variables: Set both ans and mul to 1 as we'll be building products.

  2. Process each word: Split the string s by spaces and iterate through each word.

  3. For each word, use a Counter (hash map) to track character frequencies:

    • Process each character at position i (1-indexed)
    • Increment the character's count in the Counter
    • Update mul by multiplying it with the new count: mul = mul * cnt[c] % mod
    • Update ans by multiplying it with the position index: ans = ans * i % mod
  4. Why this works:

    • When we multiply ans by i for each position, we're building up the factorial n! for a word of length n
    • When we multiply mul by cnt[c], we're building up the product of factorials of character frequencies
    • For example, if a character appears 3 times, we multiply by 1, then 2, then 3, giving us 3!
  5. Final calculation:

    • Use modular inverse to divide: ans * pow(mul, -1, mod) % mod
    • Python's pow(mul, -1, mod) computes the modular inverse of mul using Fermat's Little Theorem
    • This effectively performs ans / mul under modulo arithmetic

Example walkthrough with s = "aa b":

  • Word 1: "aa"
    • Character 1 ('a'): ans = 1 * 1 = 1, mul = 1 * 1 = 1
    • Character 2 ('a'): ans = 1 * 2 = 2, mul = 1 * 2 = 2
  • Word 2: "b"
    • Character 1 ('b'): ans = 2 * 1 = 2, mul = 2 * 1 = 2
  • Final: 2 * pow(2, -1, mod) % mod = 1

The result is 1, which represents the single distinct anagram "aa b" (since both 'a's in the first word are identical).

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through the solution with s = "abc aab":

Step 1: Initialize variables

  • ans = 1, mul = 1, mod = 10^9 + 7

Step 2: Process first word "abc"

  • Create a Counter for this word

  • Process each character with 1-based indexing:

    Position 1, character 'a':

    • cnt['a'] = 1
    • mul = mul * cnt['a'] = 1 * 1 = 1
    • ans = ans * 1 = 1 * 1 = 1

    Position 2, character 'b':

    • cnt['b'] = 1
    • mul = mul * cnt['b'] = 1 * 1 = 1
    • ans = ans * 2 = 1 * 2 = 2

    Position 3, character 'c':

    • cnt['c'] = 1
    • mul = mul * cnt['c'] = 1 * 1 = 1
    • ans = ans * 3 = 2 * 3 = 6

After word 1: ans = 6 (which is 3!), mul = 1 (since 1! × 1! × 1! = 1)

Step 3: Process second word "aab"

  • Create a new Counter for this word

  • Process each character:

    Position 1, character 'a':

    • cnt['a'] = 1
    • mul = mul * cnt['a'] = 1 * 1 = 1
    • ans = ans * 1 = 6 * 1 = 6

    Position 2, character 'a':

    • cnt['a'] = 2
    • mul = mul * cnt['a'] = 1 * 2 = 2
    • ans = ans * 2 = 6 * 2 = 12

    Position 3, character 'b':

    • cnt['b'] = 1
    • mul = mul * cnt['b'] = 2 * 1 = 2
    • ans = ans * 3 = 12 * 3 = 36

After word 2: ans = 36 (which is 3! × 3!), mul = 2 (which is 2!)

Step 4: Calculate final result

  • result = ans * pow(mul, -1, mod) % mod
  • result = 36 * pow(2, -1, mod) % mod
  • result = 36 / 2 = 18

Verification:

  • First word "abc" has 3! = 6 distinct permutations: "abc", "acb", "bac", "bca", "cab", "cba"
  • Second word "aab" has 3!/2! = 3 distinct permutations: "aab", "aba", "baa"
  • Total distinct anagrams = 6 × 3 = 18

The 18 distinct anagrams include combinations like "abc aab", "acb aba", "bac baa", etc.

Solution Implementation

1from collections import Counter
2
3class Solution:
4    def countAnagrams(self, s: str) -> int:
5        MOD = 10**9 + 7
6      
7        # total_anagrams tracks the product of anagrams for all words
8        total_anagrams = 1
9      
10        # denominator tracks the product of factorials of character frequencies
11        denominator = 1
12      
13        # Process each word separately
14        for word in s.split():
15            char_frequency = Counter()
16          
17            # For each character in the word
18            for position, char in enumerate(word, 1):
19                # Update character frequency
20                char_frequency[char] += 1
21              
22                # Update denominator: multiply by frequency (building factorial of frequencies)
23                denominator = denominator * char_frequency[char] % MOD
24              
25                # Update numerator: multiply by position (building n!)
26                total_anagrams = total_anagrams * position % MOD
27      
28        # Final result: (n1! * n2! * ... * nk!) / (freq1! * freq2! * ... * freqm!)
29        # Using modular inverse for division
30        return total_anagrams * pow(denominator, -1, MOD) % MOD
31
1import java.math.BigInteger;
2
3class Solution {
4    // Modulo value for preventing integer overflow
5    private static final int MOD = (int) 1e9 + 7;
6
7    /**
8     * Counts the number of distinct anagrams that can be formed from all words in the string.
9     * Each word can be rearranged independently, and the total count is the product of
10     * anagram counts for each word.
11     * 
12     * @param s Input string containing words separated by spaces
13     * @return Total number of distinct anagrams modulo 10^9 + 7
14     */
15    public int countAnagrams(String s) {
16        int stringLength = s.length();
17      
18        // Precompute factorials up to stringLength
19        // factorial[i] stores i! mod MOD
20        long[] factorial = new long[stringLength + 1];
21        factorial[0] = 1;
22        for (int i = 1; i <= stringLength; ++i) {
23            factorial[i] = factorial[i - 1] * i % MOD;
24        }
25      
26        // Initialize product to store the final result
27        long product = 1;
28      
29        // Process each word in the string
30        for (String word : s.split(" ")) {
31            // Count frequency of each character in the current word
32            int[] charFrequency = new int[26];
33            for (int i = 0; i < word.length(); ++i) {
34                ++charFrequency[word.charAt(i) - 'a'];
35            }
36          
37            // Calculate anagrams for current word using formula:
38            // (word length)! / (freq[char1]! * freq[char2]! * ... * freq[charN]!)
39          
40            // Multiply by factorial of word length (numerator)
41            product = product * factorial[word.length()] % MOD;
42          
43            // Divide by factorial of each character frequency (denominator)
44            // Using modular inverse for division under modulo
45            for (int frequency : charFrequency) {
46                product = product * 
47                    BigInteger.valueOf(factorial[frequency])
48                        .modInverse(BigInteger.valueOf(MOD))
49                        .intValue() % MOD;
50            }
51        }
52      
53        return (int) product;
54    }
55}
56
1class Solution {
2public:
3    const int MOD = 1e9 + 7;
4
5    int countAnagrams(string s) {
6        stringstream stringStream(s);
7        string word;
8        long totalPermutations = 1;
9        long duplicateFactorial = 1;
10      
11        // Process each word in the string
12        while (stringStream >> word) {
13            int charFrequency[26] = {0};
14          
15            // Calculate factorial of word length and track character frequencies
16            for (int i = 1; i <= word.size(); ++i) {
17                int charIndex = word[i - 1] - 'a';
18                ++charFrequency[charIndex];
19              
20                // Multiply by i to get i! (factorial of word length)
21                totalPermutations = totalPermutations * i % MOD;
22              
23                // Multiply by frequency to get product of factorials of duplicates
24                duplicateFactorial = duplicateFactorial * charFrequency[charIndex] % MOD;
25            }
26        }
27      
28        // Final result: (product of word length factorials) / (product of duplicate factorials)
29        // Using modular inverse: a/b mod p = a * b^(p-2) mod p (Fermat's Little Theorem)
30        return totalPermutations * modularPower(duplicateFactorial, MOD - 2) % MOD;
31    }
32
33    // Calculate (base^exponent) % MOD using binary exponentiation
34    long modularPower(long base, int exponent) {
35        long result = 1L;
36      
37        while (exponent > 0) {
38            // If exponent is odd, multiply result by base
39            if (exponent % 2 == 1) {
40                result = result * base % MOD;
41            }
42          
43            // Square the base and halve the exponent
44            base = base * base % MOD;
45            exponent /= 2;
46        }
47      
48        return result;
49    }
50};
51
1const MOD = 1e9 + 7;
2
3function countAnagrams(s: string): number {
4    const words = s.split(' ');
5    let totalPermutations = 1;
6    let duplicateFactorial = 1;
7  
8    // Process each word in the string
9    for (const word of words) {
10        const charFrequency: number[] = new Array(26).fill(0);
11      
12        // Calculate factorial of word length and track character frequencies
13        for (let i = 1; i <= word.length; i++) {
14            const charIndex = word.charCodeAt(i - 1) - 'a'.charCodeAt(0);
15            charFrequency[charIndex]++;
16          
17            // Multiply by i to get i! (factorial of word length)
18            totalPermutations = (totalPermutations * i) % MOD;
19          
20            // Multiply by frequency to get product of factorials of duplicates
21            duplicateFactorial = (duplicateFactorial * charFrequency[charIndex]) % MOD;
22        }
23    }
24  
25    // Final result: (product of word length factorials) / (product of duplicate factorials)
26    // Using modular inverse: a/b mod p = a * b^(p-2) mod p (Fermat's Little Theorem)
27    return (totalPermutations * modularPower(duplicateFactorial, MOD - 2)) % MOD;
28}
29
30// Calculate (base^exponent) % MOD using binary exponentiation
31function modularPower(base: number, exponent: number): number {
32    let result = 1;
33  
34    while (exponent > 0) {
35        // If exponent is odd, multiply result by base
36        if (exponent % 2 === 1) {
37            result = (result * base) % MOD;
38        }
39      
40        // Square the base and halve the exponent
41        base = (base * base) % MOD;
42        exponent = Math.floor(exponent / 2);
43    }
44  
45    return result;
46}
47

Time and Space Complexity

Time Complexity: O(n)

The algorithm iterates through each character in the string exactly once. Let n be the total length of the input string s.

  • Splitting the string by spaces takes O(n) time
  • For each word, we iterate through its characters once, updating the counter and performing modular arithmetic operations
  • The modular arithmetic operations (multiplication and modular inverse) are O(1) for each character
  • The final modular inverse calculation using pow(mul, -1, mod) takes O(log mod) time, which is effectively O(1) since mod is a constant

Therefore, the overall time complexity is O(n).

Space Complexity: O(k)

Where k is the number of unique characters across all words.

  • The Counter object cnt stores at most 26 entries (for lowercase English letters) per word, but it's reused for each word
  • The split operation creates a list of words, which in worst case could be O(n) if every character is a separate word, but typically would be O(w) where w is the number of words
  • Other variables (ans, mul, mod) use constant space

The dominant space usage is from the counter and the split operation, giving us O(k) space complexity where k represents the unique characters being tracked at any given time (bounded by 26 for English letters).

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

Common Pitfalls

1. Integer Overflow Without Proper Modulo Operations

Pitfall: Forgetting to apply modulo at each multiplication step, leading to integer overflow even in languages like Python that handle big integers (causing performance issues).

# WRONG: May cause performance issues with very large numbers
total_anagrams = total_anagrams * position  # Missing % MOD
denominator = denominator * char_frequency[char]  # Missing % MOD

# Later applying modulo doesn't help with intermediate overflow
return (total_anagrams * pow(denominator, -1, MOD)) % MOD

Solution: Apply modulo after every multiplication operation to keep numbers bounded:

# CORRECT: Keep numbers bounded throughout computation
total_anagrams = total_anagrams * position % MOD
denominator = denominator * char_frequency[char] % MOD

2. Incorrect Modular Inverse Calculation

Pitfall: Using regular division instead of modular inverse, which doesn't work correctly under modulo arithmetic:

# WRONG: Regular division doesn't work with modulo
return (total_anagrams // denominator) % MOD

# WRONG: Attempting to use modular arithmetic incorrectly
return (total_anagrams % MOD) // (denominator % MOD)

Solution: Use Python's built-in pow(base, -1, mod) for modular inverse (available in Python 3.8+) or implement using Fermat's Little Theorem:

# CORRECT: Using Python 3.8+ modular inverse
return total_anagrams * pow(denominator, -1, MOD) % MOD

# Alternative for older Python versions (Fermat's Little Theorem)
return total_anagrams * pow(denominator, MOD - 2, MOD) % MOD

3. Resetting Counter Between Words

Pitfall: Using a single Counter object across all words, which incorrectly accumulates character frequencies:

# WRONG: Single counter for all words
char_frequency = Counter()
for word in s.split():
    for position, char in enumerate(word, 1):
        char_frequency[char] += 1  # Accumulates across words!
        # ...

Solution: Create a new Counter for each word:

# CORRECT: Fresh counter for each word
for word in s.split():
    char_frequency = Counter()  # New counter for this word
    for position, char in enumerate(word, 1):
        char_frequency[char] += 1
        # ...

4. Off-by-One Error in Position Enumeration

Pitfall: Using 0-based indexing when building the factorial:

# WRONG: Starting from 0 breaks factorial calculation
for position, char in enumerate(word):  # position starts at 0
    total_anagrams = total_anagrams * position % MOD  # First multiply by 0!

Solution: Use 1-based indexing with enumerate(word, 1):

# CORRECT: 1-based indexing for factorial
for position, char in enumerate(word, 1):  # position starts at 1
    total_anagrams = total_anagrams * position % MOD

5. Edge Case: Empty String or Single Space

Pitfall: Not handling edge cases where the input might be empty or contain only spaces:

# Potential issue with edge cases
s = ""  # split() returns []
s = " "  # split() returns []

Solution: The current implementation handles these naturally (returns 1 for empty input), but it's good to be aware and potentially add explicit validation if needed:

def countAnagrams(self, s: str) -> int:
    if not s or not s.strip():
        return 1  # Empty string has 1 anagram (itself)
  
    # Continue with normal logic...
Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

What does the following code do?

1def f(arr1, arr2):
2  i, j = 0, 0
3  new_arr = []
4  while i < len(arr1) and j < len(arr2):
5      if arr1[i] < arr2[j]:
6          new_arr.append(arr1[i])
7          i += 1
8      else:
9          new_arr.append(arr2[j])
10          j += 1
11  new_arr.extend(arr1[i:])
12  new_arr.extend(arr2[j:])
13  return new_arr
14
1public static List<Integer> f(int[] arr1, int[] arr2) {
2  int i = 0, j = 0;
3  List<Integer> newArr = new ArrayList<>();
4
5  while (i < arr1.length && j < arr2.length) {
6      if (arr1[i] < arr2[j]) {
7          newArr.add(arr1[i]);
8          i++;
9      } else {
10          newArr.add(arr2[j]);
11          j++;
12      }
13  }
14
15  while (i < arr1.length) {
16      newArr.add(arr1[i]);
17      i++;
18  }
19
20  while (j < arr2.length) {
21      newArr.add(arr2[j]);
22      j++;
23  }
24
25  return newArr;
26}
27
1function f(arr1, arr2) {
2  let i = 0, j = 0;
3  let newArr = [];
4  
5  while (i < arr1.length && j < arr2.length) {
6      if (arr1[i] < arr2[j]) {
7          newArr.push(arr1[i]);
8          i++;
9      } else {
10          newArr.push(arr2[j]);
11          j++;
12      }
13  }
14  
15  while (i < arr1.length) {
16      newArr.push(arr1[i]);
17      i++;
18  }
19  
20  while (j < arr2.length) {
21      newArr.push(arr2[j]);
22      j++;
23  }
24  
25  return newArr;
26}
27

Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More