Start learning free

LESSON 272 OF 438 · HASH TABLE

Find the Difference in Java: O(n) with a HashMap of Counts

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 HashMap from each letter to how many times it appears in s. Then walk through t: each letter uses up one copy from the HashMap. 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 HashMap is O(1) on average: O(n) time.
The HashMap never holds more than 26 letters, so the extra space is O(1).
STEP 1 OF 8
    Ctrl + Enter to check

    JAVA · HASH TABLE

    Find The Difference in Java, explained

    Problem overview

    The method findTheDifference(String s, String t) gets two strings of lowercase letters. The string t is s shuffled with one extra letter added at a random position, and the method returns that letter as a char. The length of s is from 0 to 1000, and t is one longer.

    For s = "abcd" and t = "abcde" it returns 'e'. For s = "" and t = "y" it returns 'y'. This lesson asks for O(n) time, where n is the length of t.

    Approach: count s once, then use the counts up while reading t

    The brute-force solution counts each letter of t inside s again and again, which is O(n²). The waste is that the answer for a letter never changes, so count the letters of s once and keep the results in a HashMap from letter to count.

    Then treat the map as a supply of letters. Read t one letter at a time. For each letter, take one copy from the supply. Since t contains all the letters of s plus one more, the supply runs out exactly once: at the extra letter. The first letter of t that finds no copy left, because its count is 0 or because it was never in the map, is the answer.

    Take s = "aa" and t = "aaa". The map starts as {a=2}. The first 'a' of t uses a copy (count 1), the second uses another (count 0), and the third finds the count at 0, so 'a' is returned. The letter was in s, but it occurs once more in t, and the count shows it.

    With s = "abcd" and t = "abcde", the map is {a=1, b=1, c=1, d=1}. Reading t, the letters a, b, c and d each use their single copy and bring their counts to 0. Then e arrives: it is not in the map, so it reads as 0 and is returned. The work is two passes, one per string, instead of one pass per letter.

    Step by step

    1. Create the map counter, typed Map<Character, Integer> and backed by a HashMap. It will hold how many copies of each letter of s are still available.
    2. Loop over the letters of s with s.toCharArray().
    3. Increment each letter's count with counter.merge(c, 1, Integer::sum). merge stores 1 when the key is new, and otherwise combines the old value and 1 with Integer::sum. It replaces the usual "get, add one, put back" code.
    4. Loop over the letters of t.
    5. Check the supply with counter.getOrDefault(c, 0) == 0. A letter that is not in the map reads as 0, and so does a letter whose copies are used up. Both mean there is nothing left to match this letter with.
    6. Return the letter when the supply is empty. It is the extra one.
    7. Use up a copy otherwise, with counter.merge(c, -1, Integer::sum).
    8. Add a final return of ' ' after the loop. Java needs a return on every path, and with valid input the loop returns first.

    The solution in Java

    class Solution {
        public char findTheDifference(String s, String t) {
            Map<Character, Integer> counter = new HashMap<>();
            for (char c : s.toCharArray()) {
                counter.merge(c, 1, Integer::sum);
            }
            for (char c : t.toCharArray()) {
                if (counter.getOrDefault(c, 0) == 0) {
                    return c;
                }
                counter.merge(c, -1, Integer::sum);
            }
            return ' ';
        }
    }
    

    The code uses Map and HashMap from java.util, so a standalone file needs import java.util.*;.

    The Java details worth a look:

    • Boxing. A Map can't hold primitives, so the key type is Character and the value type is Integer. The char and int values are boxed and unboxed automatically.
    • get returns null for a missing key. Writing counter.get(c) == 0 would throw a NullPointerException for a letter that is not in the map, because Java unboxes the null to compare it. getOrDefault(c, 0) avoids that.
    • Integer::sum is a method reference to the static method Integer.sum(int, int). merge calls it with the old value and the new one.
    • Counts can sit at 0. The code never removes an entry. Merging -1 leaves a key with value 0, and the check treats 0 the same as missing.

    Complexity

    Time: O(n). One pass over s and one over t, with a constant number of map operations per letter. Each merge and getOrDefault is O(1) on average.

    Space: O(1). The strings hold lowercase letters only, so the map never has more than 26 entries.

    Edge cases and common mistakes

    • An empty s. The map stays empty, and the only letter of t reads as 0, so it is returned at once. ("", "y") gives 'y'.
    • A repeated extra letter. ("aa", "aaa") gives 'a'. Testing only whether the letter is in the map, instead of its count, would miss it.
    • Calling get on a missing key. A plain counter.get(c) == 0 throws for letters not in s, and the extra letter is often one of those. Use getOrDefault.
    • Checking after decrementing. If you merge -1 first and test afterwards, a negative count appears for the extra letter and a check for exactly 0 would miss it. Test first, then decrement.
    • Building the map from t and walking s. The idea can be flipped, but then the extra letter is the one left over in the map, which needs a final scan of the map. The version here exits early.

    Testing

    • "abcd", "abcde" gives 'e'.
    • "", "y" gives 'y'.
    • "aa", "aaa" gives 'a'.
    Keep going with JavaAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free