2514. Count Anagrams
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"
- The first word
"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:
- For each word in
s
, determining how many different ways its letters can be arranged (permutations) - 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.
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 byi
(building up the numeratorn!
) - 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 value10^9 + 7
to prevent overflow
Step-by-step Implementation:
-
Initialize variables: Set both
ans
andmul
to 1 as we'll be building products. -
Process each word: Split the string
s
by spaces and iterate through each word. -
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
- Process each character at position
-
Why this works:
- When we multiply
ans
byi
for each position, we're building up the factorialn!
for a word of lengthn
- When we multiply
mul
bycnt[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!
- When we multiply
-
Final calculation:
- Use modular inverse to divide:
ans * pow(mul, -1, mod) % mod
- Python's
pow(mul, -1, mod)
computes the modular inverse ofmul
using Fermat's Little Theorem - This effectively performs
ans / mul
under modulo arithmetic
- Use modular inverse to divide:
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
- Character 1 (
- Word 2:
"b"
- Character 1 (
'b'
):ans = 2 * 1 = 2
,mul = 2 * 1 = 2
- Character 1 (
- 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 EvaluatorExample 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)
takesO(log mod)
time, which is effectivelyO(1)
sincemod
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
objectcnt
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 beO(w)
wherew
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...
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
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
Backtracking Template Prereq DFS with States problems dfs_with_states Combinatorial search problems Combinatorial search problems involve finding groupings and assignments of objects that satisfy certain conditions Finding all permutations combinations subsets and solving Sudoku are classic combinatorial problems The time complexity of combinatorial problems often grows rapidly with the size of
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!