Skip to content

6. Valid Anagram

🔗 LeetCode Problem: 242. Valid Anagram
📊 Difficulty: Easy
🏷️ Topics: Hash Table, String, Sorting

Problem Statement

Given two strings s and t, return true if t is an anagram of s, and false otherwise.

An anagram is a word or phrase formed by rearranging the letters of a different word or phrase, typically using all the original letters exactly once.

Example 1:

Input: s = "anagram", t = "nagaram"
Output: true
Explanation: Both have same letters: a(3), n(1), g(1), r(1), m(1)

Example 2:

Input: s = "rat", t = "car"
Output: false
Explanation: Different letters

Constraints: - 1 <= s.length, t.length <= 5 * 10^4 - s and t consist of lowercase English letters


🎨 Visual Understanding

The Problem Visualized

Input: s = "anagram", t = "nagaram"

String s: a n a g r a m
Count:    a(3), n(1), g(1), r(1), m(1)

String t: n a g a r a m
Count:    a(3), n(1), g(1), r(1), m(1)

Comparison: Same counts for all letters ✓
Result: true
Input: s = "rat", t = "car"

String s: r a t
Count:    r(1), a(1), t(1)

String t: c a r
Count:    c(1), a(1), r(1)

Comparison: 
  - s has 't', t doesn't ✗
  - t has 'c', s doesn't ✗
Result: false

What Makes an Anagram?

Valid Anagram Requirements:
1. Same length
2. Same characters
3. Same frequency of each character

Examples:
"listen" → "silent" ✓ (rearrange letters)
"hello" → "world"   ✗ (different letters)
"aab" → "aba"       ✓ (same letters, same count)
"aab" → "aaa"       ✗ (different frequency)

Different Approaches Comparison

Approach 1: Sorting
"anagram" → sort → "aaagmnr"
"nagaram" → sort → "aaagmnr"
Compare: "aaagmnr" == "aaagmnr" ✓

Time: O(n log n)
Space: O(n) for sorted strings

Approach 2: Frequency Array
Count s: [3,0,0,1,0,0,1,0,0,0,0,0,1,1,0,0,0,1,0,1,0,0,0,0,0,0]
         a b c d e f g h i j k l m n o p q r s t u v w x y z
Count t: [3,0,0,1,0,0,1,0,0,0,0,0,1,1,0,0,0,1,0,1,0,0,0,0,0,0]
Compare arrays: Equal ✓

Time: O(n)
Space: O(1) - fixed 26 slots

Approach 3: HashMap
Count s: {a:3, n:1, g:1, r:1, m:1}
Count t: {a:3, n:1, g:1, r:1, m:1}
Compare maps: Equal ✓

Time: O(n)
Space: O(1) - at most 26 keys

🎯 Approach 1: Sorting

The Most Natural Thinking 💭

Core Logic:

If two strings are anagrams:
→ They contain the same letters
→ Sorting both will produce identical results

Steps:
1. Check if lengths are equal (quick filter)
2. Sort both strings
3. Compare sorted strings

Why This Works: - Sorting arranges letters in same order - If anagrams, sorted versions are identical - Simple and intuitive approach

Trade-off: - Sorting costs O(n log n) - Not optimal but easy to implement

Implementation

public boolean isAnagram(String s, String t) {
    // Quick check: different lengths can't be anagrams
    if (s.length() != t.length()) {
        return false;
    }

    // Convert to char arrays and sort
    char[] sChars = s.toCharArray();
    char[] tChars = t.toCharArray();

    Arrays.sort(sChars);
    Arrays.sort(tChars);

    // Compare sorted arrays
    return Arrays.equals(sChars, tChars);
}

Step-by-Step Execution: s = "anagram", t = "nagaram"

Initial Check:
├─ s.length() = 7
├─ t.length() = 7
└─ Lengths equal ✓ Continue

Convert to arrays:
├─ sChars = ['a','n','a','g','r','a','m']
└─ tChars = ['n','a','g','a','r','a','m']

After sorting:
├─ sChars = ['a','a','a','g','m','n','r']
└─ tChars = ['a','a','a','g','m','n','r']

Compare:
├─ Position 0: 'a' == 'a' ✓
├─ Position 1: 'a' == 'a' ✓
├─ Position 2: 'a' == 'a' ✓
├─ Position 3: 'g' == 'g' ✓
├─ Position 4: 'm' == 'm' ✓
├─ Position 5: 'n' == 'n' ✓
└─ Position 6: 'r' == 'r' ✓

Result: true

⏰ Time: O(n log n) - sorting dominates
💾 Space: O(n) - char arrays


🎯 Approach 2: Frequency Array (Optimal for lowercase only)

Thought Process 💭

Core Logic:

Key Insight: Only 26 possible characters (a-z)

Strategy:
1. Count frequency of each character in s
2. Subtract frequencies while processing t
3. If all counts remain 0 → anagram!

Why This Works: - Each character maps to array index (a=0, b=1, ... z=25) - Increment for s, decrement for t - If anagram, all counts return to 0

Why This is Better: - O(n) single pass through both strings - O(1) space - fixed 26 slots regardless of input size - No sorting overhead

Implementation

public boolean isAnagram(String s, String t) {
    // Quick check: different lengths can't be anagrams
    if (s.length() != t.length()) {
        return false;
    }

    // Frequency array for 26 lowercase letters
    int[] count = new int[26];

    // Count characters in both strings
    for (int i = 0; i < s.length(); i++) {
        count[s.charAt(i) - 'a']++;  // Increment for s
        count[t.charAt(i) - 'a']--;  // Decrement for t
    }

    // Check if all counts are zero
    for (int c : count) {
        if (c != 0) {
            return false;  // Frequency mismatch
        }
    }

    return true;
}

Step-by-Step Execution: s = "anagram", t = "nagaram"

Initial: count = [0,0,0,...,0] (26 zeros)

Processing both strings simultaneously:
═══════════════════════════════════════════════════════════════════

i=0: s[0]='a', t[0]='n'
├─ s: 'a'-'a'=0 → count[0]++ → count[0]=1
├─ t: 'n'-'a'=13 → count[13]-- → count[13]=-1
└─ count = [1,0,0,0,0,0,0,0,0,0,0,0,0,-1,0,...]

i=1: s[1]='n', t[1]='a'
├─ s: 'n'-'a'=13 → count[13]++ → count[13]=0
├─ t: 'a'-'a'=0 → count[0]-- → count[0]=0
└─ count = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...]

i=2: s[2]='a', t[2]='g'
├─ s: 'a'-'a'=0 → count[0]++ → count[0]=1
├─ t: 'g'-'a'=6 → count[6]-- → count[6]=-1
└─ count = [1,0,0,0,0,0,-1,0,0,0,0,0,0,0,0,...]

i=3: s[3]='g', t[3]='a'
├─ s: 'g'-'a'=6 → count[6]++ → count[6]=0
├─ t: 'a'-'a'=0 → count[0]-- → count[0]=0
└─ count = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...]

i=4: s[4]='r', t[4]='r'
├─ s: 'r'-'a'=17 → count[17]++ → count[17]=1
├─ t: 'r'-'a'=17 → count[17]-- → count[17]=0
└─ count = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...]

i=5: s[5]='a', t[5]='a'
├─ s: 'a'-'a'=0 → count[0]++ → count[0]=1
├─ t: 'a'-'a'=0 → count[0]-- → count[0]=0
└─ count = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...]

i=6: s[6]='m', t[6]='m'
├─ s: 'm'-'a'=12 → count[12]++ → count[12]=1
├─ t: 'm'-'a'=12 → count[12]-- → count[12]=0
└─ count = [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,...]

Final count array: all zeros! ✓
Result: true

Another Example: s = "rat", t = "car"

Initial: count = [0,0,0,...,0]

i=0: s='r', t='c'
  count[17]++ → count[17]=1
  count[2]-- → count[2]=-1

i=1: s='a', t='a'
  count[0]++ → count[0]=1
  count[0]-- → count[0]=0

i=2: s='t', t='r'
  count[19]++ → count[19]=1
  count[17]-- → count[17]=0

Final count: [0,0,-1,0,...,0,1,0] (has non-zero values)
Result: false

⏰ Time: O(n) - single pass
💾 Space: O(1) - fixed 26 slots


✅ Approach 3: HashMap (Universal Solution)

The Breakthrough Insight 💡

Core Logic:

Why HashMap?
- Works for ANY character set (not just a-z)
- Unicode, special characters, etc.
- More flexible than fixed array

Strategy:
1. Count frequency of each char in s
2. Decrease count for each char in t
3. If any count becomes negative → not anagram
4. If any count remains positive → not anagram

When to Use: - Unicode characters allowed - Mixed case (uppercase + lowercase) - Special characters included - More general solution

Implementation

public boolean isAnagram(String s, String t) {
    if (s.length() != t.length()) {
        return false;
    }

    HashMap<Character, Integer> count = new HashMap<>();

    // Count characters in s
    for (char c : s.toCharArray()) {
        count.put(c, count.getOrDefault(c, 0) + 1);
    }

    // Decrease counts for t
    for (char c : t.toCharArray()) {
        if (!count.containsKey(c)) {
            return false;  // Character not in s
        }
        count.put(c, count.get(c) - 1);
        if (count.get(c) < 0) {
            return false;  // Too many of this character in t
        }
    }

    return true;
}

Step-by-Step Execution: s = "anagram", t = "nagaram"

Phase 1: Count characters in s
═══════════════════════════════════════════════════════════════════
Process 'a': count = {a:1}
Process 'n': count = {a:1, n:1}
Process 'a': count = {a:2, n:1}
Process 'g': count = {a:2, n:1, g:1}
Process 'r': count = {a:2, n:1, g:1, r:1}
Process 'a': count = {a:3, n:1, g:1, r:1}
Process 'm': count = {a:3, n:1, g:1, r:1, m:1}

Phase 2: Decrease counts for t
═══════════════════════════════════════════════════════════════════
Process 'n': count = {a:3, n:0, g:1, r:1, m:1}
Process 'a': count = {a:2, n:0, g:1, r:1, m:1}
Process 'g': count = {a:2, n:0, g:0, r:1, m:1}
Process 'a': count = {a:1, n:0, g:0, r:1, m:1}
Process 'r': count = {a:1, n:0, g:0, r:0, m:1}
Process 'a': count = {a:0, n:0, g:0, r:0, m:1}
Process 'm': count = {a:0, n:0, g:0, r:0, m:0}

All counts reached 0 ✓
Result: true

⏰ Time: O(n)
💾 Space: O(1) - at most 26 keys for lowercase


🔍 Edge Cases

Case 1: Same string
Input: s = "abc", t = "abc"
Output: true
Strategy: Identical strings are anagrams

Case 2: Empty strings
Input: s = "", t = ""
Output: true
Strategy: Empty is anagram of empty

Case 3: Different lengths
Input: s = "a", t = "ab"
Output: false
Strategy: Early exit on length check

Case 4: One character difference
Input: s = "ab", t = "ac"
Output: false

Case 5: Same letters, different frequency
Input: s = "aab", t = "aaab"
Output: false
Reason: Different counts

Case 6: All same character
Input: s = "aaaa", t = "aaaa"
Output: true

Case 7: Reversed string
Input: s = "abc", t = "cba"
Output: true
Strategy: Order doesn't matter

Case 8: Unicode characters (if allowed)
Input: s = "你好", t = "好你"
Output: true
Strategy: HashMap handles Unicode

Common Mistakes

Mistake 1: Not checking length first
 Wrong: Process both strings without length check
 Right: Early exit if lengths differ

Mistake 2: Case sensitivity issues
 Wrong: Treat 'A' and 'a' as different
 Right: Convert to lowercase first (if needed)

Mistake 3: Off-by-one in array indexing
 Wrong: count[s.charAt(i) - 'A']  // Wrong for lowercase
 Right: count[s.charAt(i) - 'a']  // Correct for lowercase

Mistake 4: Not handling negative counts
 Wrong: Only check if count[i] == 0 at end
 Right: Also check for negative during processing

Mistake 5: Creating two separate count arrays
 Wrong: Use two arrays and compare
 Right: Use one array, increment/decrement

🎯 Key Takeaways

Algorithm Comparison

Approach 1: Sorting
  Time: O(n log n)
  Space: O(n)
  Use: Simple, easy to code

Approach 2: Frequency Array (BEST for lowercase)
  Time: O(n)
  Space: O(1) - fixed 26
  Use: Optimal for a-z only

Approach 3: HashMap
  Time: O(n)
  Space: O(1) - at most 26
  Use: Universal (Unicode, mixed case)

🔑 The Core Insight

Anagram = Same letters with same frequency

Three ways to verify:
1. Sort and compare (simple but slower)
2. Count frequency and match (optimal)
3. Count with increment/decrement (space-efficient)

Best approach depends on constraints:
→ Lowercase only? Frequency array
→ Unicode/mixed? HashMap
→ Quick code? Sorting

🎯 Pattern Recognition

Problem Type: Character Frequency Comparison
Core Pattern: Count and match

When to Apply:
✓ Anagram detection
✓ String permutation checking
✓ Character frequency comparison
✓ "Same letters, different order"

Key Techniques:
→ Frequency counting (array or map)
→ Increment/decrement pattern
→ Early termination on length mismatch

Related Problems:
- Group Anagrams (LC 49)
- Find All Anagrams in String (LC 438)
- Permutation in String (LC 567)
- Valid Palindrome (LC 125)

🧠 Interview Strategy

Step 1: "Need to check if strings have same letters with same frequency"
Step 2: "Sorting works but is O(n log n)"
Step 3: "Frequency counting is O(n) - better"
Step 4: "For lowercase only, use 26-size array"
Step 5: "For general case, use HashMap"

📝 Quick Revision Notes

🎯 Core Concept:

Anagram = same characters with same frequency. Count characters in one string, verify counts match in other string.

⚡ Quick Implementation:

public boolean isAnagram(String s, String t) {
    if(s.length() != t.length()) {
        return false;
    }

    // Approach 1 - using freq array (works only for lower case letters)
    // int[] count = new int[26];
    // for(int i = 0; i < s.length(); i++) {
    //     count[s.charAt(i) - 'a']++; // let s increase the count
    //     count[t.charAt(i) - 'a']--; // let t decrease the count            
    // }

    // for(int i = 0; i < count.length; i++) {
    //     if(count[i] != 0) {
    //         return false; // if they are not anagrams, some char mismatch lead to non-zero
    //     }
    // }

    // return true;

    // Approach 2 - using map (works for all chars)
    HashMap<Character, Integer> map = new HashMap<>();
    for(int i = 0; i < s.length(); i++) {
        map.put(s.charAt(i), map.getOrDefault(s.charAt(i), 0) + 1);
        map.put(t.charAt(i), map.getOrDefault(t.charAt(i), 0) - 1);
    }

    for(int value : map.values()) {
        if(value != 0) {
            return false;
        }
    }

    return true;
}

🔑 Key Insights:

  • Early exit on length mismatch
  • Frequency array for fixed character set (O(1) space)
  • HashMap for general case (Unicode, mixed case)
  • Increment/decrement pattern saves space
  • Single pass through both strings simultaneously

🎪 Memory Aid:

"Same letters, same frequency = Anagram!"
Think: "Count up for s, count down for t, all should be zero"


  • Group Anagrams (LC 49)
  • Find All Anagrams in String (LC 438)
  • Permutation in String (LC 567)

Happy practicing! 🎯