Problem overview
The string t is the string s shuffled, with one extra lowercase letter added at a random position. findTheDifference(s string, t string) returns the extra letter as a rune. The string s can be empty, and t is always exactly one letter longer.
For s = "abcd" and t = "abcde" the answer is 'e'. For s = "" and t = "y" it is 'y'. This lesson asks for O(n) time, where n is the length of t.
Approach: count s once, then spend the counts
The brute-force solution counts a letter in both strings for every letter of t, which makes it O(n²). Most of that work repeats. A letter that occurs five times in t is counted five times in each string.
A map removes the repetition. Walk through s once and record how many copies of each letter it contains. Then walk through t and treat the map as a supply of copies: every letter of t uses up one copy. Every letter of t that came from s finds a copy waiting. The extra letter is the one that finds none, because its supply ran out, or because s never held it.
Take s = "aab" and t = "abaa". The map from s is a: 2, b: 1. Reading t: the first a leaves 1 copy, b leaves 0, the second a leaves 0. The last a finds a count of 0, so it is the extra letter.
Step by step
- Create the map with
make(map[rune]int)and store it incounter. The keys are letters (runes) and the values are the number of unused copies. - Loop over
swithfor _, c := range s, discarding the index. - Count each letter with
counter[c]++. A key that isn't in the map reads as 0, so the first sighting of a letter works without any special case. - Loop over
tthe same way. - Test for an exhausted supply with
counter[c] == 0. This one comparison covers two situations: the letter is missing from the map, or its count has already dropped to 0. - Return the letter when the test succeeds. It is the extra one.
- Otherwise spend a copy with
counter[c]--. The order matters: the test comes before the decrement. - Add the fallback return after the loop. Go needs a
returnon every path, although the guarantee abouttmeans the loop always returns first.
The solution in Go
func findTheDifference(s string, t string) rune {
counter := make(map[rune]int)
for _, c := range s {
counter[c]++
}
for _, c := range t {
if counter[c] == 0 {
return c
}
counter[c]--
}
return ' '
}
The Go details worth a closer look:
- A missing key gives the zero value. Reading
counter['z']from a map that has no'z'returns 0 and does not add the key. Because of this,counter[c]++andcounter[c] == 0are both safe on a letter that was never stored. To tell "absent" from "present with 0", use the two-value formn, ok := counter[c]. This solution doesn't need the difference. - Always create the map. A variable declared as
var counter map[rune]intis a nil map. Reading from it works and returns 0, but writing to it panics withassignment to entry in nil map.makegives a usable map. rangeover a string yields runes. The loop variablecis arune, which is why the map's key type isruneand the function can returncdirectly. Indexing,s[i], would give bytes instead, a different type.- Printing the result. A
runeis an integer.fmt.Printlnshows101for'e', whilestring(r)orfmt.Printf("%c", r)shows the letter. - Map order doesn't matter here. Go iterates a map in a random order, but this solution never iterates the map. It only looks up and updates single keys.
Complexity
Time: O(n). The first loop runs over s and the second over t, which together are about 2n steps. Each map read or write takes O(1) on average.
Space: O(1). The strings hold lowercase letters only, so the map never has more than 26 entries, no matter how long the strings are. Its size is bounded by the alphabet, not by n. A plain array of 26 counters would work the same way, but the map keeps the code short and doesn't depend on the letters' codes.
Edge cases and common mistakes
- Empty
s. The map stays empty, and the only letter oftfinds a count of 0, so it is returned. - The extra letter repeats a letter of
s.s = "aab",t = "abaa"returnsa. The count catches it on the thirda, even thoughais in the map. - Decrementing before testing. If the decrement runs first, a count of 0 becomes -1 and the test
== 0never fires for that letter. Test first, then decrement. - Counting
tas well. Building maps for both strings and comparing them works, but it uses more code than a single map that is spent down. - Forgetting the nil map. Declaring the map with
varand nomakepanics on the firstcounter[c]++.
Testing
s = "abcd",t = "abcde"givese.s = "",t = "y"givesy.s = "aab",t = "abaa"givesa.
