Problem overview
The method findTheDifference(String s, String t) receives two strings of lowercase letters. The string t is s shuffled, with one extra letter added somewhere. The method returns that extra letter as a char. The length of s is from 0 to 1000, and t is one longer.
For s = "abcd" and t = "abcde" the result is 'e', and for s = "" and 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: XOR cancels the pairs
Counting letters with a hash map is already linear, but it keeps a map. XOR, written ^ in Java, gets the answer with a single number. It combines two numbers bit by bit: a result bit is 1 when the two input bits differ. Three facts matter here:
x ^ xis 0: a number cancels itself.x ^ 0isx: zero changes nothing.- The order does not matter, so XORing a list of numbers gives the same result in any order.
Every letter has a character code, a number. Start with 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 because order doesn't matter, each pair meets and cancels to 0. The one letter without a partner is the extra one. XORing it with 0 leaves its own code, and that code is the answer.
For s = "abcd" and t = "abcde": the codes of a, b, c, d appear twice and cancel, and e (code 101) appears once. The running value ends at 101, which is 'e'. For a repeated extra letter such as s = "aa" and t = "aaa", the code of 'a' appears five times: two pairs cancel and one copy is left.
Step by step
- Declare
resultas acharset to 0. It is the accumulator. Zero is the neutral value for XOR, so the first letter XORed in simply becomes the value. - Loop over the letters of
swiths.toCharArray(), naming each onec. - XOR each letter into
resultwithresult ^= c. - Loop over the letters of
tthe same way. - XOR each of those in too. After this loop, every letter that has a partner has been XORed in an even number of times and has vanished.
- Return
result. It already has typechar, so no conversion is needed.
The solution in Java
class Solution {
public char findTheDifference(String s, String t) {
char result = 0;
for (char c : s.toCharArray()) {
result ^= c;
}
for (char c : t.toCharArray()) {
result ^= c;
}
return result;
}
}
The Java details worth a look:
charis a number. A Javacharis an unsigned 16-bit number, sochar result = 0;is legal and bitwise operators work on it. Printingresultshows the letter, while(int) resultshows its code.^=keeps the type. Java promotes the operands of^toint, soresult ^ cis anint. The compound formresult ^= ccasts the answer back tocharautomatically. The long form does not compile:
char result = 0;
char c = 'e';
result = result ^ c; // does not compile
result = (char) (result ^ c); // compiles
The compiler reports incompatible types: possible lossy conversion from int to char for the first assignment. The cast in the second line fixes it, and ^= does that cast for you.
- No fallback return. Unlike the hash-map version, this method always ends at the final
return result;, so there is no unreachable code. - Two loops, one accumulator. The loops could run in the other order, because XOR doesn't care about order.
Complexity
Time: O(n). Each letter of s and t is read once and costs one XOR.
Space: O(1). The only state is one char. The toCharArray() calls do allocate copies of the strings, so if you want strictly constant space, use s.charAt(i) in an index loop instead. The XOR logic stays the same.
Edge cases and common mistakes
- An empty
s. Only the letter oftis XORed into 0, so("", "y")gives'y'. - A repeated extra letter.
("aa", "aaa")gives'a': five copies of the code, two pairs cancel, one is left. - Writing
result = result ^ c. It fails to compile, as shown above. Use^=or add the cast. - Starting from a non-zero value. The accumulator must start at 0. Any other start would be XORed into the answer and change it.
- Using
+or-instead of XOR. Adding the codes oftand subtracting the codes ofsinto anintalso finds the letter. XOR does the same with a plaincharand can never overflow, since the result always fits in 16 bits. - Using it when more than one letter is extra. XOR only isolates one lonely letter. If two letters were added, their codes would be mixed together in the result.
Testing
"abcd","abcde"gives'e'."","y"gives'y'."aa","aaa"gives'a'."abcd","dbeca"gives'e'.
