Facebook Pixel

242. Valid Anagram

EasyHash TableStringSorting
Leetcode Link

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" and t = "nagaram", the function should return true because both strings contain the same characters with the same frequencies (a:3, n:1, g:1, r:1, m:1).
  • If s = "rat" and t = "car", the function should return false 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.

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

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 than s, so they cannot be anagrams - return false

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 Evaluator

Example 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":

  1. Both strings have length 3, so we continue
  2. Build counter from s = "rat": Counter = {'r': 1, 'a': 1, 't': 1}
  3. Process t = "car":
    • Character 'c': Counter['c'] = 0 - 1 = -1 βœ— (negative!)

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.

Discover Your Strengths and Weaknesses: Take Our 3-Minute Quiz to Tailor Your Study Plan:

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

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

Load More