Problem Walkthrough

Valid Anagram LeetCode 242 Deep Dive

LeetCode 242 Valid Anagram asks whether two strings s and t are anagrams — same characters, same frequencies, different arrangement. The O(n log n) approach sorts both strings and compares them: if sorted(s) == sorted(t), they are anagrams. The optimal O(n) approach uses frequency counting: an array of 26 ints for lowercase letters (increment for s, decrement for t, check all zeros) or a HashMap for the Unicode follow-up. The single-counter optimization uses one map, incrementing for s and decrementing for t, checking all values are zero at the end. The Unicode follow-up is the key interview extension: sorting still works but is O(n log n); a HashMap frequency count scales to all 150,000+ Unicode characters in O(n) time and O(k) space where k is the number of distinct characters.

7 min read|

Valid Anagram

LeetCode 242

Problem Overview

LeetCode 242 — Valid Anagram — gives you two strings s and t and asks you to return true if t is an anagram of s, and false otherwise. An anagram is a rearrangement of the characters of one string to form another: t must contain exactly the same characters as s with exactly the same frequencies, just in a potentially different order. For example, s = "anagram" and t = "nagaram" returns true because both contain the characters a(3), n(1), g(1), r(1), m(1). But s = "rat" and t = "car" returns false because the character sets differ.

The problem is rated Easy on LeetCode and is one of the most common introductory string problems. Its real value lies in the discussion it opens: two distinct approaches (sorting vs frequency counting) with different time and space trade-offs, and the Unicode follow-up that forces you to generalize from a fixed-alphabet assumption to an open character set. Together these dimensions make it a richer interview problem than its Easy rating suggests.

The constraints for the standard problem state that s and t consist of lowercase English letters only, which means the alphabet has exactly 26 characters. This constraint is what enables the int[26] array optimization. The follow-up question explicitly relaxes this: what if the input contains Unicode characters? A robust solution must either handle this naturally (HashMap) or be extended to do so.

  • Input: two strings s and t consisting of lowercase English letters (standard version)
  • Output: true if t is an anagram of s, false otherwise
  • An anagram uses the same characters with the same frequencies in any order
  • Constraints: 1 <= s.length, t.length <= 5 * 10^4
  • Follow-up: what if the input strings contain Unicode characters?

Sorting Approach

The simplest approach is to sort both strings and compare the results. If sorted(s) == sorted(t), they are anagrams — the sorted form of any string is a canonical representation of its character multiset, so two strings are anagrams if and only if their sorted forms are identical. For s = "anagram" and t = "nagaram", both sort to "aaagmnr", so the comparison returns true.

The time complexity of the sorting approach is O(n log n) where n is the length of the longer string, dominated by the sort. The space complexity is O(n) for the sorted copies of the strings (Python's sorted() returns a list; Java's Arrays.sort requires converting to a char array). Always add the length check as an early exit: if len(s) != len(t), return false immediately without sorting — if the lengths differ, the strings cannot be anagrams.

The sorting approach is simple and easy to remember, making it useful when you need a quick correct solution. However, it is not optimal. If the interviewer explicitly asks for O(n) time, sorting does not qualify. The sorting approach also makes an implicit assumption: that the characters are sortable in a reasonable way, which is generally true for Unicode but may have nuance with certain normalization forms.

💡

Sorting is the one-liner: sorted(s) == sorted(t) in Python — but frequency counting is O(n) and shows deeper understanding

In Python, sorted(s) == sorted(t) is the entire solution in one line — sort both strings and compare. It is correct, readable, and easy to produce under pressure. However, sorting is O(n log n), not O(n). In an interview, lead with the sort if you need a quick start, then proactively offer the frequency counting approach as the O(n) optimization. This demonstrates both breadth (knowing multiple solutions) and depth (understanding the complexity difference). The sort approach also works perfectly for the Unicode follow-up without any changes.

Frequency Counting with Array

For strings consisting of lowercase English letters, the optimal approach uses an integer array of size 26 — one slot for each letter. Iterate over s and increment count[c - 'a'] for each character c. Then iterate over t and decrement count[c - 'a']. Finally, check that every element of count is zero: if any is non-zero, the frequencies differ and the strings are not anagrams. If all are zero, the strings are anagrams.

An equivalent single-pass formulation is possible: iterate over s and t simultaneously, incrementing for s and decrementing for t, then check all zeros. This is the same algorithm in slightly different form. The key insight is that the count array acts as a difference counter: positive values mean s has more of that character, negative values mean t has more. All zeros means the distributions are identical.

The time complexity is O(n) for iterating over both strings plus O(1) for checking 26 array positions. The space complexity is O(1) — the array is fixed size regardless of input length. This is the optimal solution for the lowercase-only version of the problem and is the approach interviewers most want to see when they ask for O(n) time.

  1. 1Check lengths first: if len(s) != len(t), return false immediately
  2. 2Create count array of size 26, initialized to zero
  3. 3For each character c in s: count[ord(c) - ord('a')] += 1
  4. 4For each character c in t: count[ord(c) - ord('a')] -= 1
  5. 5Return true if all elements of count are zero, false otherwise

HashMap Alternative

Instead of a fixed-size array, use a HashMap (or Python Counter) to count character frequencies. Build a frequency map for s, then for each character in t, decrement its count in the map (or check if it exists). If any character in t is not in the map or has a zero count, the strings are not anagrams. Alternatively, build two maps and compare them for equality.

The Python Counter makes this trivial: Counter(s) == Counter(t) is the entire solution — Counter is a subclass of dict that counts hashable objects, and two Counters are equal if they have the same keys with the same values. In Java, you build a HashMap<Character, Integer> manually: for each character in s, increment its count; for each character in t, decrement its count; then check that all values are zero or that the map has no non-zero entries.

The HashMap approach has O(n) time complexity and O(k) space complexity where k is the number of distinct characters. For the lowercase-only version, k <= 26, so it is effectively O(1) space — but for the Unicode follow-up, k can be much larger. The HashMap approach scales to any character set without modification, making it the preferred solution when Unicode support is required.

ℹ️

The int[26] array works for fixed alphabets; HashMap scales to Unicode's 150,000+ characters without wasting space

The int[26] array trick relies on knowing the alphabet size is exactly 26. It is space-optimal for lowercase English but cannot handle Unicode directly — a Unicode code point can be up to 0x10FFFF (1,114,111), making a full array impractical. A HashMap uses only as much space as there are distinct characters in the actual input strings, making it O(k) where k is the distinct character count. For most practical strings, k is much smaller than the full Unicode range. For the follow-up, the HashMap is the right answer: it is still O(n) time and O(k) space, with no other changes needed.

Single-Counter Optimization

Instead of building two separate frequency maps and comparing them, use a single counter: iterate over s and t simultaneously, incrementing the counter for each character in s and decrementing for each character in t. After processing both strings, check that all counter values are zero. This avoids allocating two maps and the overhead of a map equality check.

The single-counter approach is equivalent to the two-map comparison but more memory-efficient in practice because you maintain one data structure instead of two. It also exits early in spirit: if you check counts after each position, you could detect a discrepancy and exit — though in practice most implementations do a single final pass. The two key properties are: (1) only one counter is created, and (2) the final check for all-zeros is O(k) where k is the number of distinct characters.

This optimization matters most in memory-constrained environments or when the strings are very long with many distinct characters. For the typical competitive programming scenario, the difference is negligible. Its value as an interview talking point is that it shows you think about implementation details beyond just correctness — a sign of engineering maturity.

  1. 1Check lengths first: if len(s) != len(t), return false
  2. 2Create one counter (dict or int[26])
  3. 3For i in range(len(s)): counter[s[i]] += 1, counter[t[i]] -= 1
  4. 4Return true if all values in counter are zero
  5. 5This avoids building two separate maps and doing a map equality check

Code Walkthrough Python and Java

Python — Counter approach: from collections import Counter; def isAnagram(s, t): return Counter(s) == Counter(t). One line. Python — array approach: def isAnagram(s, t): if len(s) != len(t): return False; count = [0] * 26; for c in s: count[ord(c) - ord('a')] += 1; for c in t: count[ord(c) - ord('a')] -= 1; return all(x == 0 for x in count). The array approach is O(n) time and O(1) space, optimal for the lowercase version. For Unicode: replace count = [0] * 26 with count = {} and use count.get(c, 0) for lookups.

Java — int[26] approach: public boolean isAnagram(String s, String t) { if (s.length() != t.length()) return false; int[] count = new int[26]; for (char c : s.toCharArray()) count[c - 'a']++; for (char c : t.toCharArray()) count[c - 'a']--; for (int x : count) if (x != 0) return false; return true; }. The early length check prevents unnecessary work. The final loop over 26 elements is O(1). For the Unicode follow-up, replace int[26] with HashMap<Character, Integer> and update accordingly.

Both Python and Java solutions handle all edge cases: single-character strings, all-same-character strings, and strings with many repeated characters. The length check as a first line is a best practice that applies to both languages and both approaches — it is a constant-time O(1) short-circuit that eliminates all cases where anagram is impossible without any character inspection.

⚠️

Always check lengths first: if len(s) != len(t), return false immediately without any counting

The length check is a critical early exit that applies regardless of which approach you use. If s and t have different lengths, they cannot be anagrams — one has more characters than the other. Returning false immediately avoids building frequency maps or sorting strings that are provably non-anagrams. In an interview, explicitly stating "I check lengths first as an O(1) optimization" demonstrates attention to performance details. This is not just an optimization — it also prevents subtle bugs in implementations that might not naturally handle unequal-length strings correctly in the counting loop.

Ready to master algorithm patterns?

YeetCode flashcards help you build pattern recognition through active recall and spaced repetition.

Start practicing now