Problem overview
The method isAnagram receives two strings, s and t, and returns True if t is an anagram of s: the same letters rearranged, each used exactly as many times as before. Otherwise it returns False. Both strings consist of lowercase English letters and each has between 1 and 50,000 characters.
For s = "anagram" and t = "nagaram" the answer is True, since both strings contain three a, and one each of g, m, n and r. For s = "rat" and t = "car" the answer is False, because t has a c that s lacks.
Approach: sorting removes the order
Two strings are anagrams exactly when they contain the same letters with the same counts. The only thing that differs between anagrams is the order of the letters. Sorting removes that difference: sort both strings, and anagrams turn into identical sequences.
For "anagram" and "nagaram", both sort to a a a g m n r. For "rat" and "car", the sorted versions are a r t and a c r, which differ. So the whole problem reduces to sorting twice and comparing.
The lesson asks for O(n log n) time and O(n) extra space, which is exactly what two sorts and a comparison cost. Faster solutions exist: counting the letters takes O(n) time. This lesson takes the sorting route.
Step by step
- Sort
s. The variablesortedSholdssorted(s). The built-insortedaccepts any iterable, and a string is an iterable of characters, so it returns a new list of the letters in alphabetical order. - Sort
tthe same way intosortedT. Treating both strings identically is what makes the two results comparable. - Return whether they are equal. The
==operator on two lists compares them element by element. Equal lists mean the same letters in the same order, which, after sorting, means the same letters with the same counts. Lists of different lengths are never equal, so no separate length check is needed.
The solution in Python
class Solution:
def isAnagram(self, s: str, t: str) -> bool:
sortedS = sorted(s)
sortedT = sorted(t)
return sortedS == sortedT
Python details worth knowing:
sorted()returns a list, not a string.sorted("anagram")is['a', 'a', 'a', 'g', 'm', 'n', 'r']. Comparing that list with a string never givesTrue:sorted("anagram") == "aaagmnr"isFalse. Both sides of the comparison have to be lists, or both strings, for example by wrapping each result in"".join(...).sorted()versus.sort(). Strings have nosortmethod, because they are immutable. For a list,l.sort()sorts in place and returnsNone, so writingx = l.sort()leavesxequal toNone.sorted()leaves its argument alone and returns a new list.- List equality is deep.
==compares length first in effect and then each pair of elements, and characters compare by their code, so the result is a plainTrueorFalse.
Complexity
Time: O(n log n + m log m), where n and m are the lengths of s and t. Each sort costs O(k log k) for a string of length k, and the final comparison costs at most O(n). When the lengths are equal this is O(n log n).
Space: O(n + m). sorted builds a new list for each string and leaves the originals untouched.
Edge cases and common mistakes
- Different lengths.
s = "a"andt = "ab"give two lists of different lengths, which are never equal, so the result isFalsewith no extra check. - Same letters, different counts.
"ab"and"abb"fail on length. For equal lengths,"aab"and"abb"sort toa a banda b band compare as different. Checking only that every letter ofsappears somewhere intwould wrongly accept such a pair. - Identical strings. A string is always an anagram of itself, and the method returns
True. - Mixing a list and a string. As above,
sorted(s) == tis alwaysFalse, even for real anagrams. - Assuming sorting is free. Sorting is the dominant cost, which is why this solution is
O(n log n)rather than linear.
Testing
s = "anagram",t = "nagaram"givesTrue.s = "rat",t = "car"givesFalse.s = "a",t = "ab"givesFalse.s = "aab",t = "abb"givesFalse.
