Valid Anagram Solution
Given two strings s and t, return true if t is an anagram of s, and false otherwise. An anagram uses every letter of the original exactly once.
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 formed by rearranging the letters of another, using every original letter exactly once.
Brute Force โ Sort & Compare O(n log n)
Sort both strings alphabetically. If they're anagrams, their sorted forms will be identical. Compare the sorted character arrays directly.
An anagram is a rearrangement โ so two anagrams always produce the same sequence when sorted. If sorted(s) == sorted(t), every character appears the same number of times in both.
| Metric | Value |
|---|---|
| Time | O(n log n) โ dominated by sorting |
| Space | O(n) โ sorted copies of both strings |
Frequency Array โ O(n)
Two strings are anagrams if and only if every character appears the same number of times in both. Count character frequencies in s, then subtract frequencies from t โ if the array is all zeros at the end, it's an anagram.
s, decrement when consuming for t.
- Step 1: If
s.length โ t.lengthโ returnfalseimmediately (different lengths can never be anagrams). - Step 2: Create an
int[26]array (one slot per lowercase letter). - Step 3: For each character in
s, incrementcount[c - 'a']. - Step 4: For each character in
t, decrementcount[c - 'a']. - Step 5: If any slot is non-zero โ return
false. Otherwise โ returntrue.
- Any time a problem involves comparing character composition of two strings โ anagrams, permutations, rearrangement checks โ think frequency counting.
- The signal is "same characters, possibly different order." This pattern appears in LC 242, LC 49 (Group Anagrams โ sort or frequency key), LC 438 (Find All Anagrams โ sliding window + frequency), and LC 567 (Permutation in String).
- Instead of building two separate frequency maps and comparing them, we use one array as a balance sheet.
- Characters from
sdeposit (+1), characters fromtwithdraw (โ1). - If deposits equal withdrawals for every letter, the balance is zero everywhere โ confirming identical composition.
- This halves the comparisons and uses half the memory of two maps.
Visual Walkthrough
Trace through s = "anagram", t = "nagaram":
- We never compare strings character by character. The frequency array captures the composition of each string.
- If deposits from
sperfectly cancel withdrawals fromt, the strings are anagrams โ regardless of character order.
Code โ Java & Python
Complexity Analysis
| Approach | Time | Space | Trade-off |
|---|---|---|---|
| Sort + Compare | O(n log n) | O(n) | Readable one-liner; sort overhead unnecessary for bounded alphabet |
| HashMap (general) | O(n) | O(k) | Works for Unicode; HashMap overhead vs array for small k |
| Frequency Array โ optimal | O(n) | O(1) | Fixed 26-slot array โ fastest, no hashing overhead |
- A HashMap works and handles Unicode โ but for the given constraint (lowercase English letters only), an
int[26]is faster. - No hash computation, no boxing integers, no collision resolution.
- The array has O(1) space because its size is fixed regardless of input length.
- Use a HashMap when the character set is unbounded (follow-up question).
Edge Cases & Pitfalls
| Case | Input | Why It Matters |
|---|---|---|
| Different lengths | s="abc", t="ab" | Early exit โ if lengths differ, it's never an anagram. Guards against index errors. |
| Single character | s="a", t="a" | Minimum valid input. Trivially true โ both are the same letter. |
| Same string | s="listen", t="listen" | A string is always an anagram of itself. Frequency counts cancel perfectly. |
| Repeated characters | s="aab", t="aba" | Duplicates must appear the same number of times. Frequency array handles this naturally. |
| Same letters, wrong count | s="aacc", t="ccac" | false โ s has aร2,cร2 but t has cร3,aร1. The array catches the imbalance. |
| All same character | s="aaaa", t="aaaa" | Trivially true. Only slot 'a' is used; it increments to 4 then decrements back to 0. |
s="ab" and t="a", processing only up to the shorter string's length would miss the extra character. Always check s.length() != t.length() first โ or loop both strings independently (which naturally produces non-zero counts for mismatched lengths).