The problem
You get two strings s and t. Return True if t is an anagram of s, meaning it uses exactly the same characters, the same number of times, in any order. Otherwise return False.
Input: s = "anagram", t = "nagaram"
Output: True
Input: s = "rat", t = "car"
Output: FalseBoth of the first pair have three as and one each of n, g, r, m. "rat" has a t and "car" has a c, so they differ.
How to think about it
Start with brute force. For each character of s, find and remove a matching character from t. Each search is O(n), so that’s O(n²). A better first idea is to sort both strings and compare: anagrams sort to the same string. That is O(n log n) and very short to write: sorted(s) == sorted(t).
The key insight: an anagram is defined by counts, not order. Two strings are anagrams if and only if every character appears the same number of times in both. So build a frequency table for each string and compare the tables. Building a table is one O(n) pass; comparing two tables is O(number of distinct characters).
In Python, Counter is that frequency table. Counter("anagram") is a dictionary {'a': 3, 'n': 1, 'g': 1, 'r': 1, 'm': 1}, and two Counters compare equal when they have the same keys with the same counts. Different lengths automatically give different counts, so you don’t need a separate length check.
Which pattern is this? Hash map frequency counting. The tell is “same letters, any order”, or “rearrangement of”.
Approach
- Count the characters of
sinto a frequency map. - Count the characters of
tinto another. - Return whether the two maps are equal.
If you can’t use Counter, do it with one array of 26 zeros: add one for each character of s, subtract one for each of t, and check that every entry is zero at the end.
Solution
from collections import Counter
def is_anagram(s, t):
return Counter(s) == Counter(t) # same characters with the same counts
# is_anagram("anagram", "nagaram") -> TrueDry run
On s = "anagram", t = "nagaram":
| Character | Count in s |
Count in t |
Equal? |
|---|---|---|---|
a |
3 | 3 | yes |
n |
1 | 1 | yes |
g |
1 | 1 | yes |
r |
1 | 1 | yes |
m |
1 | 1 | yes |
Every count matches, so it returns True. For "rat" and "car", t appears in only the first Counter and c only in the second, so it returns False.
Complexity
- Time: O(n). Each Counter is one pass over its string, and comparing them is O(k) for k distinct characters.
- Space: O(1) for lowercase English letters, since each table holds at most 26 keys. For general Unicode input it’s O(k).
Edge cases and pitfalls
- Different lengths:
"a"vs"aa"gives counts{a: 1}and{a: 2}, so it returnsFalsewith no extra check. - Empty strings: two empty Counters are equal, so
is_anagram("", "")isTrue. - Case and spaces:
"Listen"and"silent"differ becauseLis notl. Normalise first if the problem says to. - Using a set (
set(s) == set(t)) ignores counts."aab"and"abb"would wrongly pass. - Unicode follow-up: Counter already handles any character. The 26-slot array version does not.
Related problems
- Group Anagrams (LeetCode 49): use a canonical form of each word as a dictionary key.
- Ransom Note (LeetCode 383): one count table must “fit inside” the other instead of being equal.
- Find All Anagrams in a String (LeetCode 438): compare counts inside a sliding window.
- 1.Two Sum (LeetCode 1)
- 2.Valid Anagram (LeetCode 242)
- 3.Contains Duplicate (LeetCode 217)
- 4.Ransom Note (LeetCode 383)
- 5.First Unique Character in a String (LeetCode 387)
- 6.Majority Element (LeetCode 169)
- 7.Isomorphic Strings (LeetCode 205)
- 8.Group Anagrams (LeetCode 49)
- 9.Top K Frequent Elements (LeetCode 347)
- 10.Longest Consecutive Sequence (LeetCode 128)
- 11.Subarray Sum Equals K (LeetCode 560)
- 12.Valid Sudoku (LeetCode 36)
- 13.LRU Cache (LeetCode 146)