Problem overview
The method FindTheDifference receives two strings of lowercase letters, s and t. The string t is s shuffled with one extra letter added. The method returns that extra letter as a char, in O(n) time and with O(1) extra space.
For s = "abcd" and t = "abcde" the answer is 'e'. For s = "" and t = "y" it is 'y'.
Approach: let equal letters cancel
Counting letters with a dictionary is linear in time, but it stores counts. This lesson needs constant space and uses the bitwise XOR operator, ^, which has three properties:
x ^ xis 0: a value cancels itself.x ^ 0isx: XOR with zero changes nothing.- The order doesn't matter:
a ^ b ^ cequalsc ^ a ^ b.
Every letter has a numeric code, its UTF-16 value, and 'a' is 97. XOR together the codes of all letters of s and all letters of t. Each letter of s has an equal partner in t, and because the order is irrelevant, each such pair cancels to 0. Only the extra letter has no partner, so the final value is its code. Converting that value back to a char gives the answer.
For s = "abcd" and t = "abcde": the letters a, b, c and d each appear twice in total and cancel, leaving 0 ^ 'e', which is 101, the code of 'e'.
Step by step
- Declare an accumulator
char result = (char)0;. Starting at 0 is safe because XOR with 0 leaves any value unchanged. The cast turns the integer 0 into thecharwith code 0. - Loop over
swithforeach (char c in s). - XOR each letter into the accumulator with
result ^= c;. - Loop over
tthe same way. - XOR each of its letters in too. After this loop, every letter that occurs in both strings has been XOR-ed an even number of times and has cancelled.
- Return
result. It holds the code of the extra letter, and it is already achar, so no conversion is needed.
The solution in C#
class Solution {
public char FindTheDifference(string s, string t) {
char result = (char)0;
foreach (char c in s) {
result ^= c;
}
foreach (char c in t) {
result ^= c;
}
return result;
}
}
The C# details worth knowing:
^oncharvalues produces anint. C# convertscharoperands tointbefore doing arithmetic or bitwise operations. So'a' ^ 'b'has typeint(here, 3).- Why
^=works andresult = result ^ cdoesn't. Writingresult = result ^ c;withresultacharis a compile error:Cannot implicitly convert type 'int' to 'char'. An explicit conversion exists (are you missing a cast?). The compound formresult ^= cconverts theintback tocharfor you. The long form needs a cast:result = (char)(result ^ c);. - Values stay in range. Both operands are 16-bit values, and the XOR of two 16-bit values fits in 16 bits, so the conversion back to
charnever loses anything. - The order is free. XOR in
tfirst andssecond and the result is the same. - The accumulator can be an
int. Declaringint result = 0and returning(char)resultis equivalent. The lesson keeps it acharso that the return needs no conversion.
Complexity
Time: O(n). Each letter of s and t is read once, and one XOR costs constant time.
Space: O(1). The only extra memory is result and the loop variable. There is no dictionary or array that grows with the input.
Edge cases and common mistakes
- Empty
s. The first loop does nothing, and the second XORs the single letter oftinto 0.s = "",t = "y"gives'y'. - The extra letter repeats a letter of
s. Fors = "aab"andt = "baab", the letteraappears four times in total andbthree times. The fouras cancel in pairs, and two of the threebs cancel, leaving'b'. - Using
+instead of^. Adding the codes oftand subtracting the codes ofsalso finds the letter, but XOR needs no subtraction and no sign to think about. - Starting from a letter instead of 0. The accumulator must start at 0, or its starting value stays mixed into the answer.
- Assuming it works for any input. XOR cancels exact pairs. It relies on the guarantee that
tissplus one letter. If the strings broke that rule, the result would be meaningless.
Testing
s = "abcd",t = "abcde"gives'e'.s = "",t = "y"gives'y'.s = "aab",t = "baab"gives'b'.s = "ab",t = "bca"gives'c'.
