Skip to content

Coding patternsBlind 75

Valid Anagram

Two words are anagrams when every letter appears the same number of times. Solved two ways, sorting in O(n log n) and letter counting in O(n), with a step-through, code in five languages and the Big O worked out from the code.

You are given two strings, text and target_text. Return true if target_text is an anagram of text, and false otherwise.

An anagram uses exactly the same letters, each one the same number of times, just in a different order. So “listen” and “silent” are anagrams, but “seen” and “send” are not.

Example 1

Input: text = "night", target_text = "thing"

Output: true

Both words use g, h, i, n and t once each.

text
n0i1g2h3t4
target_text
t0h1i2n3g4

Example 2

Input: text = "seen", target_text = "send"

Output: false

text has two e’s and no d. target_text has one e and a d.

Example 3

Input: text = "ab", target_text = "abc"

Output: false

The words have different lengths, so they cannot hold the same letters.

  • 1 ≤ text.length, target_text.length ≤ 50,000 (below, n is the length of text)
  • text and target_text hold only lowercase English letters, a to z.
Which letters can appear?
Only a to z, lowercase. That means 26 counters are enough.
Does a word count as its own anagram?
Yes. "abc" and "abc" give true. The order does not have to change.
Can the lengths differ?
Yes. Then the answer is false right away, before any other work.
What if the input could be any Unicode text?
Then 26 counters are not enough. Use a hash map from character to count instead. The idea stays the same.

The order of the letters does not matter. Only how many times each letter appears matters. When a problem only cares about counts, count them: a small table of counters, or a hash map, replaces any search. That is the hashing pattern.

This problem has two approaches. Pairing each letter by searching the other word works too, but it is slower and teaches nothing new.

Count letters O(n)

Keep one counter per letter, all starting at 0. Walk text and add 1 for each letter. Then walk target_text and take 1 for each letter.

If a counter ever goes below 0, then target_text uses that letter more often than text does. Stop and return false. If we finish, every counter must be exactly 0. Here is why: both words have the same length, so we added and took away the same total. No counter is below 0, so none can be above 0 either.

Why it is correct: two words are anagrams exactly when every letter count matches, and the counters measure the difference in every count at once.

text
n0i1g2h3t4
target_text
t0h1i2n3g4
letterghint
count00000
  1. if len(text) != len(target_text): return False
  2. count = [0] * 26
  3. for letter in text:
  4. count[letter] += 1
  5. for letter in target_text:
  6. count[letter] -= 1
  7. if count[letter] < 0:
  8. return False
  9. return True

Same length, so go on. Start one counter per letter at 0. Only the letters that appear are shown here.

1 / 12
Solution.javasolution.pySolution.cssolution.gosolution.ts
class Solution {
// Count letters: add 1 for each letter of text, take 1 for each letter of targetText.
// Two passes, so O(n) time. Only 26 counters, so O(1) memory.
public boolean isAnagram(String text, String targetText) {
// Different lengths can never hold the same letters.
if (text.length() != targetText.length()) return false;
int[] count = new int[26];
for (char letter : text.toCharArray()) count[letter - 'a']++;
for (char letter : targetText.toCharArray()) {
count[letter - 'a']--;
// targetText uses this letter more often than text does.
if (count[letter - 'a'] < 0) return false;
}
// Same length and nothing went below 0, so every counter is 0.
return true;
}
}
Time O(n)Space O(1)Here is how we get these from the code, in four steps.
  1. 1 Find the work that repeats

    The lit lines change one counter and check it. Finding a letter’s counter is a direct jump to slot letter − 'a', with no search. So each run of either loop is 1 step. There are two loops, one after the other, not one inside the other.

    1. if len(text) != len(target_text): return False
    2. count = [0] * 26
    3. for letter in text:
    4. count[letter] += 1
    5. for letter in target_text:
    6. count[letter] -= 1
    7. if count[letter] < 0:
    8. return False
    9. return True
  2. 2 Count the runs on Example 1

    Example 1 has 5 letters in each word. The first loop runs 5 times, once per letter of “night”. The second loop runs 5 times, once per letter of “thing”.

    5 + 5 = 10 runs.

    looplettersruns
    over textnight5
    over target_textthing5
    total
    5 + 5 = 10
  3. 3 Write the count for any n

    Each loop walks one word of length n, one after the other:

    n + n = 2n

    Check it with Example 1: 2 × 5 = 10.

  4. 4 Keep only the part that grows fastest

    Drop the 2. Two passes instead of one double the work, but when n doubles, 2n still only doubles. What is left is n.

    So the time is O(n).

  5. S Space: count the extra memory the same way

    The counters are an array of 26 numbers, one per letter. That is 26 for a 5-letter word or for a 50,000-letter word. It does not grow with n, so the space is O(1).

    count (26 numbers)letter

2,000 random word pairs of 1 to 7 letters from a, b and c. Half are real anagrams, and many others are one letter or one count off. The sort both and the count letters gave the same answer every time.

Largest inputsResult
50,000 letters in each word (the most allowed), a true anagram, so both words are read to the end. Count letters.0.01 s, and about 0.001 MB of extra memory: just the 26 counts.
50,000 letters in each word. Sort both.0.01 s, and about 1 MB of extra memory: two sorted copies of 50,000 letters.