Problem overview
The method isAnagram(s, t) receives two strings of lowercase letters and returns true when t is an anagram of s: the same letters, each used exactly as many times, in any order. Otherwise it returns false.
With s = "anagram" and t = "nagaram", both words contain three a, one g, one m, one n and one r, so the answer is true. With s = "rat" and t = "car", the letters t and c don't match, so the answer is false. Each string has between 1 and 50,000 letters, and the two lengths can differ.
This lesson asks for O(n log n) time and O(n) extra space, and notes that faster solutions exist and come later.
Approach: sort both words, then compare
The obvious difficulty is that anagrams list the same letters in different orders. Sorting removes the order. If both words are put in alphabetical order, two anagrams turn into the very same string, and two words that are not anagrams stay different.
"anagram" sorted: "aaagmnr"
"nagaram" sorted: "aaagmnr" same, so an anagram
"rat" sorted: "art"
"car" sorted: "acr" different, so not an anagram
The statement is exact about why this works: two strings are anagrams if and only if their sorted versions are equal. If the letters and their counts are the same, sorting puts them in the same order. If a letter count differs, the sorted strings differ somewhere, whatever the sort did.
Strings of different lengths need no special case. A shorter sorted string can't equal a longer one, so the comparison returns false by itself.
Step by step
- Sort the letters of
sinto a string namedsortedS, declared withlet. A string has nosortmethod of its own, so the code does three things in a chain:split('')turns the string into an array of one-letter strings,sort()orders that array, andjoin('')glues the letters back into a string. - Do the same for
tand call the resultsortedT. Treating both words the same way makes their sorted forms directly comparable. - Return
sortedS === sortedT. The result of the comparison is already a boolean, so the method returns it directly instead of using anif.
The solution in JavaScript
var algorithms = require('algorithms')
class Solution {
/**
* @param {string} s
* @param {string} t
* @return {boolean}
*/
isAnagram(s, t) {
let sortedS = s.split('').sort().join('');
let sortedT = t.split('').sort().join('');
return sortedS === sortedT;
}
}
The first line belongs to the lesson's starter code: it loads a helper library that this solution never uses. The work happens in the three lines of isAnagram. Some JavaScript details worth a closer look:
- Why the array must be joined back.
===on two arrays asks whether they are the very same array in memory, not whether they hold the same letters.[1, 2] === [1, 2]isfalse. Two strings are compared letter by letter, and"abc" === "abc"istrue. That is why the code joins the sorted letters into strings before comparing. sort()with no comparator compares as text. It converts each element to a string and orders by UTF-16 code units. That is a problem for numbers:[10, 9, 1].sort()gives[1, 10, 9]. For single lowercase letters, text order is exactly alphabetical order, so no comparator is needed here.sort()changes the array it is called on and returns the same array. That is harmless here, becausesplit('')made a fresh array that nothing else uses. The original strings are never modified, because strings are immutable.split('')with an empty separator splits between every character, so"rat".split('')is['r', 'a', 't'].letversusconst. The variables are never reassigned, soconstwould also work. The lesson useslet, and both behave the same here.
Complexity
Time: O(n log n + m log m), where n and m are the lengths of s and t. Sorting s costs O(n log n) and sorting t costs O(m log m). Splitting and joining are O(n) and O(m), and the final string comparison is at most as long as the shorter string, so these are smaller than the sorts. When the two strings have the same length, this is O(n log n).
Space: O(n + m). The method builds an array of letters and a sorted string for each input, so the extra memory grows with the lengths of the two strings.
A faster method counts the letters instead of sorting them, which takes O(n) time. It needs a table of counts, and that is a different lesson.
Edge cases and common mistakes
- Different lengths.
"ab"and"abc"sort to"ab"and"abc", which are not equal, so the method returnsfalsewith no extra check. - Repeated letters.
"aab"and"abb"have the same set of letters but different counts. Sorted, they are"aab"and"abb", so the result isfalse. Comparing sets of letters would wrongly saytrue. - Identical words. A word is an anagram of itself, so
isAnagram("a", "a")istrue. - Comparing arrays with
===. If the code left outjoin(''), the method would returnfalsefor every input, even for two equal words, because two different arrays are never===. - Sorting only one of the strings. Sorting
sand comparing it with the unsortedtonly works whenthappens to be in order already. - Calling
sorton a string. Strings have no such method, sos.sort()throws aTypeError. The conversion to an array comes first.
Testing
s = "anagram",t = "nagaram"givestrue.s = "rat",t = "car"givesfalse.s = "aab",t = "abb"givesfalse.s = "ab",t = "abc"givesfalse.s = "a",t = "a"givestrue.
