Problem overview
The string t is s shuffled with one extra lowercase letter inserted. findTheDifference(s string, t string) returns that letter as a rune. The string s may be empty.
For s = "abcd" and t = "abcde" the result is 'e', and for s = "", t = "y" it is 'y'. This lesson asks for O(n) time and O(1) extra space, where n is the length of t.
Approach: let equal letters cancel
Counting with a map already runs in O(n) time. This lesson does without the map, using the XOR operator, ^ in Go. XOR compares two numbers bit by bit and gives a 1 where the bits differ. Three of its properties solve this problem:
x ^ xis 0: a number cancels itself.x ^ 0isx: zero changes nothing.- Order and grouping don't matter, so
a ^ b ^ aequalsbeven though the twoavalues are not next to each other.
Every letter has a number, its code point. Start an accumulator at 0 and XOR in the code of every letter of s and then of every letter of t. Each letter of s has an equal partner in t, and the shuffle doesn't matter because order is irrelevant to XOR. The partners cancel to 0 in pairs, and the one letter without a partner is left. If a letter occurs three times in t and twice in s, two of the three cancel against the two in s, and one remains.
For s = "abcd" and t = "abcde" the accumulator ends as a ^ b ^ c ^ d ^ a ^ b ^ c ^ d ^ e, which is e, the number 101.
Step by step
- Declare the accumulator as
var result rune = 0. It has to be arune, because XOR in Go needs both operands to have the same type and the letters fromrangeare runes. - Loop over
swithfor _, c := range s. The loop yields each letter as a rune, and_ignores the position. - XOR each letter in with
result ^= c, shorthand forresult = result ^ c. - Loop over
tthe same way. - XOR those letters in too. After this loop, every letter that appears in both strings has been mixed in twice per matching pair and has cancelled out.
- Return
result. It already has the right type, so no conversion and no fallback return are needed.
The solution in Go
func findTheDifference(s string, t string) rune {
var result rune = 0
for _, c := range s {
result ^= c
}
for _, c := range t {
result ^= c
}
return result
}
The Go details worth a closer look:
^is XOR when written between two operands. In Go, the same symbol also has a unary form:^xflips every bit ofx. Go has no~operator. Hereresult ^= cuses the binary compound form.- Types must match. Mixing a byte and a rune, as in
x ^ rwith auint8and arune, doesn't compile:invalid operation: x ^ r (mismatched types uint8 and rune). That is why the accumulator is a rune rather than anintor a byte. - The answer is a number. Returning
runemeansfmt.Printlnprints 101 fore. Usestring(r)orfmt.Printf("%c", r)to show the letter. - No extra return. The earlier solutions need a fallback
return ' ', because a return inside a loop might never run as far as the compiler can tell. This version always reaches the finalreturn. - No overflow worry. XOR never carries, so the result stays within the range of the inputs.
Complexity
Time: O(n). One XOR per letter, over s and then t, which is about 2n cheap operations. There are no lookups or sorting.
Space: O(1). The only extra memory is the single rune accumulator. The brute-force version also used O(1) space but needed O(n²) time, and the map version needed a map of up to 26 entries.
Edge cases and common mistakes
- Empty
s. Nothing is XORed in the first loop, and the second loop leaves just the code of the single letter int. - The extra letter repeats another one.
s = "aab",t = "abaa"returnsa:aappears 5 times in total, so one is left after the pairs cancel. - Starting the accumulator at 1 or any other nonzero value. That value would be XORed into the answer. Start at 0.
- Relying on the idea elsewhere. XOR cancellation finds the odd one out. Here it works because every other letter occurs an even number of times in total across the two strings.
Testing
s = "abcd",t = "abcde"givese.s = "",t = "y"givesy.s = "aab",t = "abaa"givesa.
