242. Valid Anagram
Problem Description
You are given two strings s
and t
. Your task is to determine if string t
is an anagram of string s
.
An anagram means that string t
contains exactly the same characters as string s
, but possibly in a different order. In other words, you can rearrange the letters of s
to form t
.
Return true
if t
is an anagram of s
, and false
otherwise.
For example:
- If
s = "anagram"
andt = "nagaram"
, the function should returntrue
because both strings contain the same characters with the same frequencies (a:3, n:1, g:1, r:1, m:1). - If
s = "rat"
andt = "car"
, the function should returnfalse
because they contain different characters.
The key insight is that two strings are anagrams if and only if they contain the exact same characters with the exact same frequencies - just potentially arranged in a different order.
Intuition
To check if two strings are anagrams, we need to verify that they contain the exact same characters with the exact same frequencies. Think of it like having two bags of letters - they're anagrams if both bags contain identical sets of letters.
The most straightforward observation is that anagrams must have the same length. If one string is longer than the other, they can't possibly be anagrams since one would have extra characters.
Once we know the strings have equal length, we need to count and compare character frequencies. We can think of this as creating an inventory of characters in the first string, then checking if the second string matches this inventory exactly.
A clever approach is to use a counter or hash table to track character frequencies from the first string s
. Then, as we go through the second string t
, we "consume" characters from our counter by decrementing their counts. If at any point we try to use a character that we don't have enough of (count goes below 0), we know t
contains characters that aren't available in the right quantities from s
, so they can't be anagrams.
This approach is efficient because we only need one pass through each string, and we can exit early if we find a mismatch. If we successfully process all characters in t
without any count going negative, the strings must be anagrams.
Solution Approach
The solution uses a counting approach with a hash table to verify if two strings are anagrams.
Step 1: Length Check
First, we compare the lengths of strings s
and t
. If len(s) != len(t)
, we immediately return false
since strings of different lengths cannot be anagrams.
Step 2: Character Counting
We create a counter (hash table) to store the frequency of each character in string s
. The Counter(s)
creates a dictionary where keys are characters and values are their frequencies. For example, if s = "aab"
, the counter would be {'a': 2, 'b': 1}
.
Step 3: Character Verification
We iterate through each character c
in string t
:
- Decrement the count for character
c
in our counter:cnt[c] -= 1
- Check if the count becomes negative:
if cnt[c] < 0
- If any count goes below 0, it means
t
has more occurrences of that character thans
, so they cannot be anagrams - returnfalse
Step 4: Final Result
If we successfully process all characters in t
without any count going negative, return true
. This means every character in t
exists in s
with the correct frequency.
The algorithm has a time complexity of O(n)
where n
is the length of the strings, since we traverse each string once. The space complexity is O(k)
where k
is the number of unique characters in string s
, which in the worst case could be O(26)
for lowercase English letters, making it effectively O(1)
.
This approach is optimal because it only requires one pass through each string and can detect non-anagrams early by returning as soon as a mismatch is found.
Ready to land your dream job?
Unlock your dream job with a 3-minute evaluator for a personalized learning plan!
Start EvaluatorExample Walkthrough
Let's walk through the solution with a concrete example where s = "listen"
and t = "silent"
.
Step 1: Length Check
- Length of
s = "listen"
is 6 - Length of
t = "silent"
is 6 - Since lengths are equal, we proceed to the next step
Step 2: Build Character Counter from s
We create a counter from string s = "listen"
:
Counter = { 'l': 1, 'i': 1, 's': 1, 't': 1, 'e': 1, 'n': 1 }
Step 3: Process Each Character in t = "silent"
Now we iterate through each character in t
and decrement the counter:
- Character 's':
Counter['s'] = 1 - 1 = 0
β (not negative, continue) - Character 'i':
Counter['i'] = 1 - 1 = 0
β (not negative, continue) - Character 'l':
Counter['l'] = 1 - 1 = 0
β (not negative, continue) - Character 'e':
Counter['e'] = 1 - 1 = 0
β (not negative, continue) - Character 'n':
Counter['n'] = 1 - 1 = 0
β (not negative, continue) - Character 't':
Counter['t'] = 1 - 1 = 0
β (not negative, continue)
Final counter state:
Counter = { 'l': 0, 'i': 0, 's': 0, 't': 0, 'e': 0, 'n': 0 }
Step 4: Return Result
Since we processed all characters in t
without any count going negative, we return true
. The strings "listen" and "silent" are anagrams!
Counter-Example: Non-Anagram Case
Let's also see what happens with s = "rat"
and t = "car"
:
- Both strings have length 3, so we continue
- Build counter from
s = "rat"
:Counter = {'r': 1, 'a': 1, 't': 1}
- Process
t = "car"
:- Character 'c':
Counter['c'] = 0 - 1 = -1
β (negative!)
- Character 'c':
Since the counter for 'c' becomes negative (because 'c' doesn't exist in s
), we immediately return false
. The strings are not anagrams.
Solution Implementation
1from collections import Counter
2
3class Solution:
4 def isAnagram(self, s: str, t: str) -> bool:
5 # Early return if strings have different lengths
6 if len(s) != len(t):
7 return False
8
9 # Count frequency of each character in string s
10 char_count = Counter(s)
11
12 # Iterate through string t and decrement character counts
13 for char in t:
14 char_count[char] -= 1
15 # If count goes negative, t has more of this character than s
16 if char_count[char] < 0:
17 return False
18
19 # All characters matched successfully
20 return True
21
1class Solution {
2 /**
3 * Determines if two strings are anagrams of each other.
4 * Two strings are anagrams if they contain the same characters with the same frequencies.
5 *
6 * @param s the first string to compare
7 * @param t the second string to compare
8 * @return true if the strings are anagrams, false otherwise
9 */
10 public boolean isAnagram(String s, String t) {
11 // Early return if strings have different lengths - they cannot be anagrams
12 if (s.length() != t.length()) {
13 return false;
14 }
15
16 // Array to track character frequency differences
17 // Index represents character (0 for 'a', 1 for 'b', ..., 25 for 'z')
18 int[] characterCount = new int[26];
19
20 // Process both strings simultaneously
21 // Increment count for characters in string s
22 // Decrement count for characters in string t
23 for (int i = 0; i < s.length(); i++) {
24 characterCount[s.charAt(i) - 'a']++;
25 characterCount[t.charAt(i) - 'a']--;
26 }
27
28 // Check if all character counts are zero
29 // If any count is non-zero, the strings are not anagrams
30 for (int i = 0; i < 26; i++) {
31 if (characterCount[i] != 0) {
32 return false;
33 }
34 }
35
36 // All character counts are zero, strings are anagrams
37 return true;
38 }
39}
40
1class Solution {
2public:
3 bool isAnagram(string s, string t) {
4 // Early return if strings have different lengths
5 if (s.size() != t.size()) {
6 return false;
7 }
8
9 // Create a frequency counter for 26 lowercase English letters
10 vector<int> charFrequency(26, 0);
11
12 // Process both strings simultaneously
13 for (int i = 0; i < s.size(); ++i) {
14 // Increment count for character in string s
15 ++charFrequency[s[i] - 'a'];
16 // Decrement count for character in string t
17 --charFrequency[t[i] - 'a'];
18 }
19
20 // Check if all frequency counts are zero
21 // If they are, both strings contain the same characters with same frequencies
22 return all_of(charFrequency.begin(), charFrequency.end(),
23 [](int count) { return count == 0; });
24 }
25};
26
1/**
2 * Determines if two strings are anagrams of each other.
3 * An anagram is a word formed by rearranging the letters of another word.
4 *
5 * @param s - The first string to compare
6 * @param t - The second string to compare
7 * @returns true if the strings are anagrams, false otherwise
8 */
9function isAnagram(s: string, t: string): boolean {
10 // Early return if strings have different lengths
11 if (s.length !== t.length) {
12 return false;
13 }
14
15 // Initialize character frequency counter for 26 lowercase letters
16 const characterFrequency: number[] = new Array(26).fill(0);
17
18 // Process both strings simultaneously
19 for (let i = 0; i < s.length; i++) {
20 // Increment count for character in string s
21 const sCharIndex: number = s.charCodeAt(i) - 'a'.charCodeAt(0);
22 characterFrequency[sCharIndex]++;
23
24 // Decrement count for character in string t
25 const tCharIndex: number = t.charCodeAt(i) - 'a'.charCodeAt(0);
26 characterFrequency[tCharIndex]--;
27 }
28
29 // Check if all character frequencies are zero (perfectly balanced)
30 return characterFrequency.every((frequency: number) => frequency === 0);
31}
32
Time and Space Complexity
The time complexity is O(n)
, where n
is the length of the string. The algorithm first checks if the lengths are equal (constant time), then creates a Counter for string s
which takes O(n)
time, and finally iterates through string t
once, performing constant-time operations for each character, which also takes O(n)
time. Overall, this gives us O(n) + O(n) = O(n)
.
The space complexity is O(C)
, where C
is the size of the character set. The Counter object stores at most C
unique characters. Since this problem deals with lowercase English letters, C = 26
, making the space complexity O(26) = O(1)
in terms of constant space.
Common Pitfalls
Pitfall 1: Forgetting the Length Check
One common mistake is jumping straight into character counting without first checking if the strings have equal lengths. This can lead to unnecessary computation and incorrect results.
Incorrect approach:
def isAnagram(self, s: str, t: str) -> bool:
char_count = Counter(s)
for char in t:
char_count[char] -= 1
if char_count[char] < 0:
return False
# Bug: If t is shorter than s, we might return True incorrectly
return True
Why it fails: If s = "abc"
and t = "ab"
, the function would return True
because all characters in t
exist in s
, but they're not anagrams since s
has an extra 'c'.
Solution: Always check lengths first before any other processing.
Pitfall 2: Using Default Dictionary Behavior Incorrectly
When using Counter, accessing a non-existent key returns 0 by default. However, if you use a regular dictionary, accessing a missing key throws a KeyError.
Incorrect approach with regular dict:
def isAnagram(self, s: str, t: str) -> bool:
if len(s) != len(t):
return False
char_count = {}
for char in s:
char_count[char] = char_count.get(char, 0) + 1
for char in t:
char_count[char] -= 1 # KeyError if char not in s
if char_count[char] < 0:
return False
return True
Solution: Either use Counter (which handles missing keys gracefully) or check key existence before decrementing:
for char in t: if char not in char_count: return False char_count[char] -= 1 if char_count[char] < 0: return False
Pitfall 3: Inefficient Double-Pass Verification
Some implementations count both strings and then compare the counters, which requires two full passes and additional comparison logic.
Less efficient approach:
def isAnagram(self, s: str, t: str) -> bool:
if len(s) != len(t):
return False
return Counter(s) == Counter(t)
While this works correctly and is readable, it always processes both strings completely even when an early mismatch could be detected. The provided solution can return early when it finds a character in t
that exceeds its frequency in s
, making it more efficient for non-anagram cases.
What's the output of running the following function using input 56
?
1KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12def letter_combinations_of_phone_number(digits):
13 def dfs(path, res):
14 if len(path) == len(digits):
15 res.append(''.join(path))
16 return
17
18 next_number = digits[len(path)]
19 for letter in KEYBOARD[next_number]:
20 path.append(letter)
21 dfs(path, res)
22 path.pop()
23
24 res = []
25 dfs([], res)
26 return res
27
1private static final Map<Character, char[]> KEYBOARD = Map.of(
2 '2', "abc".toCharArray(),
3 '3', "def".toCharArray(),
4 '4', "ghi".toCharArray(),
5 '5', "jkl".toCharArray(),
6 '6', "mno".toCharArray(),
7 '7', "pqrs".toCharArray(),
8 '8', "tuv".toCharArray(),
9 '9', "wxyz".toCharArray()
10);
11
12public static List<String> letterCombinationsOfPhoneNumber(String digits) {
13 List<String> res = new ArrayList<>();
14 dfs(new StringBuilder(), res, digits.toCharArray());
15 return res;
16}
17
18private static void dfs(StringBuilder path, List<String> res, char[] digits) {
19 if (path.length() == digits.length) {
20 res.add(path.toString());
21 return;
22 }
23 char next_digit = digits[path.length()];
24 for (char letter : KEYBOARD.get(next_digit)) {
25 path.append(letter);
26 dfs(path, res, digits);
27 path.deleteCharAt(path.length() - 1);
28 }
29}
30
1const KEYBOARD = {
2 '2': 'abc',
3 '3': 'def',
4 '4': 'ghi',
5 '5': 'jkl',
6 '6': 'mno',
7 '7': 'pqrs',
8 '8': 'tuv',
9 '9': 'wxyz',
10}
11
12function letter_combinations_of_phone_number(digits) {
13 let res = [];
14 dfs(digits, [], res);
15 return res;
16}
17
18function dfs(digits, path, res) {
19 if (path.length === digits.length) {
20 res.push(path.join(''));
21 return;
22 }
23 let next_number = digits.charAt(path.length);
24 for (let letter of KEYBOARD[next_number]) {
25 path.push(letter);
26 dfs(digits, path, res);
27 path.pop();
28 }
29}
30
Recommended Readings
Sorting Summary Comparisons We presented quite a few sorting algorithms and it is essential to know the advantages and disadvantages of each one The basic algorithms are easy to visualize and easy to learn for beginner programmers because of their simplicity As such they will suffice if you don't know any advanced
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
Recursion Recursion is one of the most important concepts in computer science Simply speaking recursion is the process of a function calling itself Using a real life analogy imagine a scenario where you invite your friends to lunch https assets algo monster recursion jpg You first call Ben and ask
Want a Structured Path to Master System Design Too? Donβt Miss This!