Problem overview
The function canConstruct takes two strings, ransomNote and magazine. It returns true if the note can be assembled from letters of the magazine, where each magazine letter can be used at most once, and false otherwise. The order of letters does not matter, only how many of each are available.
With ransomNote = "aa" and magazine = "abb" the answer is false: the note needs two a's and the magazine has one. With magazine = "aab" the answer is true, and the leftover b is simply unused.
The statement asks for O(n + m) time, where n and m are the string lengths, and O(Σ) extra space, where Σ is the alphabet size (26 lowercase letters here).
Approach: compare letter counts
Trying to cross letters off the magazine one by one would rescan the magazine for each note letter, which is O(n * m). Because order is irrelevant, the whole question reduces to one condition: for every letter, the magazine contains at least as many copies as the note. So count the letters of each string once, then compare the counts letter by letter.
A hash map from letter to count is the natural tool. Reading a missing key returns 0, so a letter that never appears in a string needs no special handling, neither when counting nor when comparing.
Take ransomNote = "abba" and magazine = "ababab". The note's counts are a: 2 and b: 2. The magazine's counts are a: 3 and b: 3. For a, 2 is not greater than 3; for b, 2 is not greater than 3; every other letter reads 0 against 0. No letter comes up short, so the answer is true. Change the magazine to "abbb" and a has only 1 copy against the note's 2, so the answer becomes false.
The same result could be had with a single map: count the magazine, then walk through the note and decrement, failing if a count drops below zero. This lesson keeps two maps because the comparison is then easy to read and to check.
Step by step
- Create two maps with
make,ransomFrequencyandmagazineFrequency, each of typemap[rune]int. The key is arunebecause ranging over a Go string produces runes. - Count the note. Range over
ransomNote, discard the index with_, and incrementransomFrequency[character]. - Count the magazine the same way into the second map. Reusing the name
characteris fine, because each loop variable exists only inside its own loop. - Loop over the alphabet with
charactergoing from'a'to'z'. - Compare the counts. If the note needs more copies of a letter than the magazine has, return
falseimmediately: one short letter is enough to make the note impossible. - Return
trueafter the loop, since no letter came up short.
The solution in Go
func canConstruct(ransomNote, magazine string) bool {
ransomFrequency := make(map[rune]int)
magazineFrequency := make(map[rune]int)
for _, character := range ransomNote {
ransomFrequency[character]++
}
for _, character := range magazine {
magazineFrequency[character]++
}
for character := 'a'; character <= 'z'; character++ {
if ransomFrequency[character] > magazineFrequency[character] {
return false
}
}
return true
}
The Go details:
rangeover a string yields runes. Each iteration gives the byte offset and the character as arune(an alias forint32), decoded from UTF-8. For"aé"the offsets are 0 and 1, and the rune values are 97 and 233. Indexing instead,s[i], gives a singlebyte, andéoccupies two bytes.- Why
make. A map that is only declared is nil, and writing to it panics.make(map[rune]int)creates a usable empty map. - Increment of a missing key.
ransomFrequency[character]++reads 0 for an absent key, adds one and stores the result. - A character literal is a rune. The type of
'a'isrune, socharacter := 'a'declares a rune that can be compared with<=and incremented with++, and it matches the map's key type. - Comparing an absent letter. A letter absent from the note reads as 0 in
ransomFrequency, and 0 is never greater than a count, so it cannot cause afalse.
Complexity
Time: O(n + m). Counting the note takes n steps, counting the magazine takes m steps, and the alphabet loop runs 26 times, a constant. Map operations are O(1) on average.
Space: O(Σ). Each map holds at most one entry per distinct letter, which is bounded by the alphabet size and not by the string lengths.
Edge cases and common mistakes
- Empty note.
canConstruct("", "abc")andcanConstruct("", "")aretrue: nothing is needed. - Empty magazine.
canConstruct("a", "")isfalse. - Counting with a set. A set would only record that a letter exists, so
"aa"against"ab"would wrongly pass. The counts are what matter. - Comparing the wrong direction. The failing condition is note count greater than magazine count. A magazine with extra letters is fine.
- Letters outside
atoz. The final loop only inspects lowercase ASCII letters, which is what the problem's inputs consist of. With this solution,canConstruct("A", "a")would returntruebecause the uppercase letter is never compared. If other characters were possible, loop over the keys ofransomFrequencyinstead.
Testing
"aa","abb"givesfalse."aa","aab"givestrue."abba","ababab"givestrue."","abc"givestrue."a",""givesfalse.