Start learning free

LESSON 276 OF 442 · HASH TABLE

Find the Difference in TypeScript: O(n) with a Frequency Object

Practice O(n) Read the full explanation

You are given two strings s and t.

String t is generated by random shuffling string s and then add one more letter at a random position.

Return the letter that was added to t.

 

Example 1:

Input: s = "abcd", t = "abcde"
Output: e
Explanation: 'e' is the letter that was added.

Example 2:

Input: s = "", t = "y"
Output: y

 

Constraints:

  • 0 <= s.length <= 1000
  • t.length == s.length + 1
  • s and t consist of lowercase English letters.
Note:

For this lesson, your algorithm should run in O(n) time, where n is the length of t.



The brute force counts each letter of t in s again and again. Count the letters of s only once, and keep the counts.

Store the counts in a dictionary from each letter to how many times it appears in s. Then walk through t: each letter uses up one copy from the dictionary. The first letter of t that has no copy left (its count is 0, or it was never there) is the extra one.

We walk through s once and t once, and each lookup in the dictionary is O(1) on average: O(n) time.
The dictionary never holds more than 26 letters, so the extra space is O(1).
STEP 1 OF 7
    Ctrl + Enter to check

    TYPESCRIPT · HASH TABLE

    Find The Difference in TypeScript, explained

    Problem overview

    String t is a shuffled copy of s with one extra lowercase letter added. The method findTheDifference returns that letter. With s = "abcd" and t = "abcde" the result is "e", and with s = "" and t = "y" it is "y".

    This version must run in 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 of t inside s from scratch every time, which costs a full walk per letter and O(n²) overall. The waste is clear: s never changes, so its letter counts only need to be computed once.

    Build an object that maps each letter to how many times it appears in s. Then walk through t and treat each count as a supply of copies. Each letter of t uses up one copy. Every letter of s has a partner in t, so every use finds a copy waiting, except for the extra letter. When a letter of t finds no copy left, either because its count has reached 0 or because it never appeared in s, that letter is the extra one.

    Take s = "ae" and t = "aea". Counting s gives { a: 1, e: 1 }. In t, the first "a" uses its copy and the count becomes 0. "e" does the same. The second "a" finds a count of 0, so the answer is "a". The letter was in s already, and the counts still expose it.

    Step by step

    1. Create counter as an empty object typed as a map from strings to numbers. The annotation { [key: string]: number } says that any string key holds a number.
    2. Loop over the letters of s with for...of, naming the letter c.
    3. Increase that letter's count. A letter seen for the first time has no entry, and reading it gives undefined. The expression (counter[c] || 0) turns that into 0 before adding 1.
    4. Loop over the letters of t in a second loop.
    5. Test !counter[c]. A missing entry (undefined) and a count of 0 are both falsy, so one test covers both ways of running out.
    6. Return c inside that branch. This is the extra letter.
    7. Decrement counter[c] after the test. Only a letter that still has a copy reaches this line, so the count never goes below 0.

    The solution in TypeScript

    class Solution {
        findTheDifference(s: string, t: string): string {
            const counter: { [key: string]: number } = {};
            for (const c of s) {
                counter[c] = (counter[c] || 0) + 1;
            }
            for (const c of t) {
                if (!counter[c]) {
                    return c;
                }
                counter[c]--;
            }
        }
    }
    

    The TypeScript details worth a closer look:

    • The index signature is only a type. { [key: string]: number } tells the compiler what to expect, so counter["z"] is typed as number. It is erased when the code compiles to JavaScript. At run time the object is a plain object, and reading a letter that was never stored gives undefined, not 0. That is the reason for || 0.
    • const does not freeze the object. const counter prevents reassigning the variable, and the properties can still change.
    • Truthiness. !counter[c] is true for undefined and for 0. That works because a stored count is never negative. The same shortcut would be wrong if 0 were a legitimate stored value you wanted to tell apart from "missing".
    • Plain objects are safe here. The keys are single lowercase letters, which can't clash with built-in property names such as constructor. For arbitrary string keys, a Map<string, number> is the safer choice, read with map.get(c) ?? 0.
    • The missing final return. The guarantee that t has one extra letter means the second loop always returns. The lesson's compiler settings don't flag the end of the method. An input without an extra letter would give undefined at run time, whatever the declared return type says.

    Complexity

    Time: O(n). The first loop runs once per letter of s and the second once per letter of t, which is one more than s. Each object read and write takes constant time on average, so the total is linear in n.

    Space: O(1). counter holds at most 26 keys, one per lowercase letter, however long the strings are. The size of the alphabet is a constant, so the space doesn't grow with n.

    Edge cases and common mistakes

    • Empty s. The first loop does nothing and counter stays empty. The only letter of t finds no entry, so it is returned.
    • The extra letter duplicates a letter of s. s = "a" with t = "aa" returns "a", because the second "a" finds a count of 0. A check that asked only whether the letter exists in s would fail here.
    • Decrementing before the test. A count of 0 would become -1, which is truthy, so the test would no longer see that the letter had run out. Test first, decrement second.
    • Forgetting the default. Writing counter[c] = counter[c] + 1 on a new letter gives undefined + 1, which is NaN. NaN is falsy, so the second loop would report that letter as the extra one.
    • Returning the wrong end. The answer is the letter of t that runs out, so the second loop must walk t, not s.

    Testing

    • s = "abcd", t = "abcde" gives "e".
    • s = "", t = "y" gives "y".
    • s = "aab", t = "baaa" gives "a".
    • s = "ae", t = "aea" gives "a".
    Keep going with TypeScriptAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free