Problem overview
isAnagram(s string, t string) returns true when t can be formed by rearranging all the letters of s, using each letter exactly as many times as it appears, and false otherwise. Both strings hold lowercase English letters only.
For s = "anagram" and t = "nagaram" the answer is true: the two words use the same letters in a different order. For s = "rat" and t = "car" it is false, because t has a c where s has a t.
Approach: sorting removes the order
Two words are anagrams when they differ only in the order of their letters. Sorting takes the order away. After sorting, two anagrams become the same string, and two words that aren't anagrams stay different.
For "anagram" and "nagaram", both sort to "aaagmnr". For "rat" and "car", the sorted forms are "art" and "acr", which differ. If the strings have different lengths, the sorted strings differ in length and are never equal, so no separate length check is needed.
The cost is dominated by the two sorts, O(n log n) each. The lesson accepts that price. A faster solution counts the letters instead of sorting them, and it has its own lesson.
Step by step
- Copy
sinto a byte slice with[]byte(s), namedsortedS. A Go string can't be changed in place, so sorting needs a mutable copy of its bytes. Each lowercase English letter takes exactly one byte, so the slice holds exactly the letters. - Sort it with
sort.Slice. It takes the slice and a functionless(i, j), which reports whether the item at positionibelongs before the item at positionj. Here that issortedS[i] < sortedS[j], which sorts the bytes in ascending order. - Do the same for
t: a byte slice namedsortedT, sorted the same way. Both strings need identical treatment so that their results can be compared. - Compare the results as strings. Convert each slice back with
string(...)and compare with==. The result of that comparison is the return value.
The solution in Go
func isAnagram(s string, t string) bool {
sortedS := []byte(s)
sort.Slice(sortedS, func(i, j int) bool { return sortedS[i] < sortedS[j] })
sortedT := []byte(t)
sort.Slice(sortedT, func(i, j int) bool { return sortedT[i] < sortedT[j] })
return string(sortedS) == string(sortedT)
}
The Go details worth a closer look:
- The import.
sort.Slicelives in thesortpackage, so a program that uses this function needsimport "sort". Without it, the build fails. - Strings are immutable. Assigning to a position of a string, as in
s[0] = 'x', doesn't compile (cannot assign to s[0]). Converting with[]byte(s)makes a new slice with a copy of the bytes, so sorting it leaves the originalsuntouched. - The less function is a closure. The function literal
func(i, j int) bool { ... }refers tosortedS, a variable of the enclosing function.sort.Slicepasses it positions, not values, so the function reads the items itself. - Slices can't be compared with
==. WritingsortedS == sortedTfails withslice can only be compared to nil. Converting both to strings gives something Go can compare, byte by byte. A loop over the two slices would work too, and so wouldbytes.Equal. - Bytes versus characters. Sorting bytes is correct here only because every letter is one byte. A string with accented or other non-ASCII letters has several bytes per character, and sorting the bytes would mix them up. The constraints rule that case out.
Complexity
Time: O(n log n + m log m), where n and m are the lengths of s and t. Each sort costs a length times its logarithm. Comparing two strings costs at most the length of the shorter one, which is smaller than the sorting cost. When the lengths are equal, this is O(n log n).
Space: O(n + m). The two byte slices are copies of the inputs. Converting them back to strings for the comparison may allocate copies as well, which stays within the same bound.
Edge cases and common mistakes
- Different lengths.
"ab"and"abc"sort to"ab"and"abc", which are different, so the answer isfalse. - Repeated letters.
"aab"and"abb"contain the same distinct letters, but the counts differ, so the sorted strings"aab"and"abb"differ. Checking only which letters appear would answertruewrongly. - Same word. A string is an anagram of itself, and the function returns
true. - Sorting the original. A string can't be sorted directly. Copy it into a byte slice first.
- Wrong direction in the less function. Both strings are sorted by the same function, so either ascending or descending would give a correct answer, as long as both use the same one. Mixing the two would break the comparison.
- Comparing slices directly. Convert to strings first, or compare element by element.
Testing
s = "anagram",t = "nagaram"givestrue.s = "rat",t = "car"givesfalse.s = "ab",t = "abc"givesfalse.s = "aab",t = "abb"givesfalse.
