Problem overview
The method findTheDifference(s, t) receives two strings of lowercase letters. The string t is s shuffled, with one extra letter added at some position. The method returns the extra letter as a one-character string.
For s = "abcd" and t = "abcde" the answer is "e". For s = "" and t = "y" the answer is "y". The string s can be empty, and t is always one character longer.
This lesson asks for O(n) time, where n is the length of t.
Approach: count s once, then use the counts up
The brute-force version counts a letter in both strings every time it meets that letter in t, and that repeated work makes it O(n²). The fix is to count the letters of s a single time and keep the result in a lookup table: an object that maps each letter to the number of times it appears in s.
Then walk through t. Each letter of t uses up one copy from the table. Every letter of s has its partner in t, so those letters always find a copy to use. The extra letter is the one that finds none: either its count has already dropped to 0, or it was never in the table.
Here is s = "aab" and t = "abaa". The table from s is { a: 2, b: 1 }. Walking through t:
ahas 2 copies. Use one:{ a: 1, b: 1 }.bhas 1 copy. Use it:{ a: 1, b: 0 }.ahas 1 copy. Use it:{ a: 0, b: 0 }.ahas 0 copies left, soais the extra letter.
The method can stop at that moment. It never needs to look at the rest of t.
Step by step
- Create an empty object named
counterwithconst. The variable never gets reassigned, only its contents change, soconstis right. - Loop over
swithfor...of, which gives one character at a time. - Add one to the letter's count. A letter seen for the first time has no entry, and reading a missing key gives
undefined, so the code reads the count as(counter[c] || 0): the stored number, or 0 when there is none. Then it adds 1 and stores the result back. - Loop over
tin a secondfor...of. The first loop must be completely finished before this one starts, so that the table is complete. - Check
!counter[c]. The value is falsy in two cases: the letter has no entry (undefined), or its count has reached0. Both mean "no copy left". - Return
cin that case: this is the extra letter. - Decrement
counter[c]. This line is reached only when a copy was available, so the count can't go below zero.
The solution in JavaScript
class Solution {
findTheDifference(s, t) {
const counter = {};
for (const c of s) {
counter[c] = (counter[c] || 0) + 1;
}
for (const c of t) {
if (!counter[c]) {
return c;
}
counter[c]--;
}
}
}
Some JavaScript details worth a closer look:
- A plain object as a table. The letters become property names, and
counter["a"]andcounter.amean the same thing. After the first loop on"abcd",counteris{ a: 1, b: 1, c: 1, d: 1 }. - A missing key is
undefined, not an error.counter.zon that object isundefined, and!counter.zistrue. That is why a single check covers both "never seen" and "used up". || 0versus?? 0. Here the two behave the same, because a stored count is never0at that point in the first loop. The||operator replaces every falsy value, while??replaces onlyundefinedandnull. With counts,??is the more precise habit.- A
Mapwould also work. It would usemap.get(c)andmap.set(c, ...). For lowercase letters the plain object is shorter and works fine. - No final
return. If the loop ended without finding anything, the method would returnundefined. With valid input, the extra letter always stops the loop first.
Complexity
Time: O(n). The first loop walks s, which has n - 1 letters, and the second loop walks at most t, which has n letters. Each step reads or writes one property of the object, which takes constant time on average. Two passes of constant work per letter is O(n).
Space: O(1). The object holds one entry per distinct letter, and the input only has lowercase letters, so it never has more than 26 entries, no matter how long the strings are.
Edge cases and common mistakes
- An empty
s. The first loop does nothing, socounterstays empty. The first letter oftfinds no entry, and the method returns it. Fort = "y"that is"y". - The extra letter is a duplicate. For
s = "aab"andt = "abaa"the answer is"a". A check such as "is this letter missing froms?" would not find it, but the table does, because the count ofareaches 0. - The extra letter is new. For
s = "ab"andt = "abz"there is no entry forz, so the check!counter[c]is true on that letter and"z"is returned. - Forgetting the decrement. Without
counter[c]--, the counts never go down and the second loop never finds a letter to return. - Using the table the wrong way round. Counting
tfirst and walkingswould find a letter with too many copies ins, and there is none. The loop that returns must walk the longer string,t. - Using
if (counter[c] === 0)only. That misses letters that are not in the table, where the value isundefined, not0.
Testing
s = "abcd",t = "abcde"gives"e".s = "",t = "y"gives"y".s = "aab",t = "abaa"gives"a".
