Problem
Given two strings
sandt, returntrueif they are anagrams of each other, otherwise returnfalse. Two strings are anagrams if they contain the same characters, each appearing the same number of times, in any order.
Example 1
Input: s = "racecar", t = "carrace"
Output: true
Example 2
Input: s = "jar", t = "jam"
Output: false
Example 3
Input: s = "x", t = "x"
Output: true
Constraints
1 <= len(s), len(t) <= 5 * 10^4sandtconsist of lowercase English letters.
Thinking
- Brute force idea: sort both strings and compare → . Correct, but sorting does more work than needed: we only care how many of each letter, not their order.
- Key insight: two strings are anagrams ⇔ they have the same character counts. Counting is a single pass. And since there are only 26 lowercase letters, the counts fit in a fixed-size array → extra space.
- Quick reject: if
len(s) != len(t), they can’t be anagrams. - Edge cases to remember: different lengths, same letters but different counts
(
"aab"vs"abb"), single character, identical strings.
Solution
Approach A — my first solution (sort and compare)
Complexity: Time · Space (sorted builds a new list of characters)
Short and correct. The cost is the sort: it puts every character in order, which is more than the question needs.
def is_anagram_sort(s: str, t: str) -> bool:
return sorted(s) == sorted(t)
Approach B — Counter (Pythonic)
collections.Counter builds a dict of character → count; two Counters are equal when every count matches.
Complexity: Time · Space , where is the number of distinct characters (≤ 26 here). Works for any characters, including Unicode.
from collections import Counter
def is_anagram_counter(s: str, t: str) -> bool:
return Counter(s) == Counter(t)
Approach C — fixed array of 26 counts ✅ best practice (what interviewers expect)
Add 1 for each letter of s, subtract 1 for each letter of t. They’re anagrams if every count ends at 0.
ord(c) - ord('a') maps 'a'..'z' to 0..25.
Complexity: Time · Space (always 26 integers, no matter how long the strings are).
def is_anagram(s: str, t: str) -> bool:
if len(s) != len(t):
return False
count = [0] * 26
for a, b in zip(s, t):
count[ord(a) - ord("a")] += 1
count[ord(b) - ord("a")] -= 1
return all(c == 0 for c in count)
Scratch / Experiments
Timing at the maximum input size ( characters).
Big-O isn’t the whole story in Python: sorted and Counter do their work in C,
while Approach C’s loop runs in Python, so C can be slower in wall-clock time even though
it’s the better algorithm. In an interview, C is the answer to explain; in real Python code,
Counter is the idiomatic choice.
import random, string
s = "".join(random.choices(string.ascii_lowercase, k=50_000))
t = "".join(random.sample(s, len(s))) # a shuffled copy, so it's an anagram
%timeit is_anagram_sort(s, t)
%timeit is_anagram_counter(s, t)
%timeit is_anagram(s, t)
Takeaways
- Pattern: “same elements, any order” → compare frequency counts instead of sorting.
Small fixed alphabet (e.g. 26 lowercase letters) → use an array of size 26 for space;
otherwise use a dict /
Counter. - Mistakes I made: reached for sorting, which is , when counting is .
Also skipped the cheap
len(s) != len(t)early exit. - Related problems: Contains Duplicate (#217, hash set), Group Anagrams (#49, use the sorted string or a 26-count tuple as a dict key), Find All Anagrams in a String (#438, sliding window + counts), Ransom Note (#383, counts).