Problem overview
The method findTheDifference receives strings s and t, where t is s shuffled with one extra letter added. It returns the added letter as a Character.
For s = "abcd" and t = "abcde" the answer is "e". For s = "" and t = "y" it is "y". The extra letter can also repeat a letter that s already contains: for s = "ae" and t = "aea" it is "a".
This lesson asks for O(n) time, where n is the length of t.
Approach: count once, then use up the copies
The brute-force solution counts a letter in both strings again for every letter of t, and each count walks a whole string. The waste is that the counts of s never change. They can be computed once and kept.
Build a dictionary that maps each letter to the number of times it appears in s. Then walk through t and treat the dictionary as a supply of copies: each letter of t uses up one copy of itself. Because t contains everything in s plus one more letter, every letter finds a copy to use up, except for the extra one. The first letter that finds no copy left, either because its count has dropped to 0 or because it was never in the dictionary, is the answer.
Take s = "aab" and t = "abaa". The dictionary holds a: 2 and b: 1. Walking t: the first a uses a copy (1 left), b uses its copy (0 left), the second a uses the last copy (0 left), and the third a finds a count of 0, so it is returned.
Each dictionary operation takes constant time on average, and each string is walked once, so the total is O(n).
Step by step
- Declare the dictionary.
var counter: [Character: Int] = [:]starts empty. The type annotation is needed because[:]alone doesn't say what the keys and values are. - Loop over
swithfor c in s. Each pass gives oneCharacter. - Increment that letter's count.
counter[c, default: 0] += 1reads the count, using 0 if the letter isn't there yet, adds one and stores the result. One expression handles both the first time a letter is seen and every later time. - Loop over
twith anotherfor c in t. - Check for a missing copy. If
counter[c, default: 0] == 0, the letter either never appeared insor has been used up already. - Return the letter. That letter is the extra one.
- Use up a copy. When the check fails, the letter still has a copy, so
counter[c, default: 0] -= 1removes one. This line sits after theif, so it runs only when the letter wasn't returned. - Add a fallback return after the second loop. Swift requires a return on every path. The constraints guarantee that the loop always returns, so the fallback
" "never runs.
The solution in Swift
class Solution {
func findTheDifference(_ s: String, _ t: String) -> Character {
var counter: [Character: Int] = [:]
for c in s {
counter[c, default: 0] += 1
}
for c in t {
if counter[c, default: 0] == 0 {
return c
}
counter[c, default: 0] -= 1
}
return " "
}
}
The Swift details worth a closer look:
- A plain subscript returns an optional. For a dictionary of type
[Character: Int],counter["z"]has typeInt?: it isnilwhen the key is missing andOptional(1)when the value is 1. You can't add to an optional directly. - The
default:subscript returns a plainInt.counter["z", default: 0]gives0for a missing key without changing the dictionary. Used with+=or-=, it also inserts the key when needed. Aftercounter["z", default: 0] += 1, a plaincounter["z"]givesOptional(1). - Keys are
Charactervalues.Characteris hashable, so it works as a dictionary key. The loop variablescare characters, matching the key type. - The dictionary is a value type. It belongs to this call, and nothing outside the method sees it.
- Dictionary order is unspecified. The solution never loops over the dictionary, only over the strings, so the order doesn't matter here.
Complexity
Time: O(n). The first loop runs once per letter of s, which is n - 1 times, and the second loop runs at most n times. Every dictionary read or update costs constant time on average, so the total is linear in the length of t.
Space: O(1). The dictionary holds one entry per distinct letter. The input has only lowercase English letters, so there are at most 26 entries, whatever the length of the strings.
Edge cases and common mistakes
- Empty
s. The dictionary stays empty. The first letter oftreads a count of 0 and is returned. - The extra letter repeats an existing one. In
s = "ae",t = "aea", the secondaintfinds the count at 0. Checking only whether the key exists would miss it, which is why the test is== 0and not "missing". - Decrementing before the check. If the subtraction came first, counts could go negative and the zero test would stop working. Check first, then subtract.
- Using the plain subscript in the arithmetic.
counter[c] += 1doesn't compile, becausecounter[c]is an optional and+=needs a plain number on the left. Use thedefault:form, or unwrap it yourself. - Returning at the end of the first loop. The answer is only known once
thas been walked, so the return belongs in the second loop.
Testing
s = "abcd",t = "abcde"returns"e".s = "",t = "y"returns"y".s = "aab",t = "abaa"returns"a".s = "ae",t = "aea"returns"a".
