Solving tips
- Recognize a multiset-containment question: order is irrelevant, only per-letter counts matter.
- Count both strings (Counter or a length-26 array) and check need[ch] <= have[ch] for every letter; O(m+n) time, O(1) space for the fixed alphabet.
- Efficient variant: count the magazine into 26 slots, then decrement while reading the note and fail the instant a slot goes negative.
- Pitfall: comparing letter sets ignores multiplicity ('aa' vs 'ab' fails), and count the magazine first, then spend with the note (order matters).
Problem
Youβre given two strings, ransomNote and magazine. Determine whether you can assemble the ransom note by cutting letters out of the magazine: every character of ransomNote must be matched to a distinct character of magazine (each magazine letter can be used at most once). Return True if itβs possible, False otherwise.
In other words: for every letter, does the magazine contain at least as many copies as the note needs?
Examples
- Input:
ransomNote = "a", magazine = "b" β Output: False
The magazine has no a at all.
- Input:
ransomNote = "aa", magazine = "ab" β Output: False
The note needs two as but the magazine supplies only one.
- Input:
ransomNote = "aab", magazine = "baa" β Output: True
The magazine has two as and one b β exactly enough.
Constraints
1 <= len(ransomNote), len(magazine) <= 10^5
- Both strings consist of lowercase English letters only
Expected: O(m + n) time. The 26-letter alphabet is a strong hint about the counting structure.
Think about it first
Hint 1
Does the *order* of letters in either string matter at all?
Hint 2
If order doesn't matter, the only thing that does is how many of each letter each string has. What structure captures that?
Hint 3
Count each string's letters (a hash map, or a length-26 array since it's only lowercase letters). The note is buildable iff for every letter its note-count β€ its magazine-count.
TL;DR
Count letters of both strings and compare per letter β O(m + n) time, O(1) space (26-letter alphabet).
Approach 1 β Brute force: cross letters off a copy
Simulate the cutting literally: for each character of the note, search the magazine for an unused copy and cross it off.
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
pool = list(magazine)
for ch in ransomNote:
if ch in pool:
pool.remove(ch)
else:
return False
return True
Complexity: O(n Β· m) time (each in / remove scans the pool), O(m) space.
With both strings up to 10^5, thatβs ~10^10 character comparisons in the worst case β hopeless. All that scanning just to answer βis there an unused e left?β, which a counter answers in O(1).
Approach 2 β Hash map of counts (Counter)
The insight: order is irrelevant β the note is buildable iff, letter by letter, the magazineβs supply covers the noteβs demand. Count both sides once, then compare 26 numbers.
from collections import Counter
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
need = Counter(ransomNote)
have = Counter(magazine)
return all(have[ch] >= cnt for ch, cnt in need.items())
Equivalently, not (need - have) β Counter subtraction keeps only positive deficits.
Walkthrough on ransomNote = "aab", magazine = "baa":
need = {a: 2, b: 1}, have = {b: 1, a: 2}.
- Check
a: have 2 β₯ need 2 β ok.
- Check
b: have 1 β₯ need 1 β ok.
- All letters covered β
True. Matches the expected output.
On "aa" vs "ab": need = {a: 2}, have = {a: 1, b: 1}; check a: 1 β₯ 2 fails β False.
Complexity: O(m + n) time; O(1) space β at most 26 keys per counter.
Approach 3 β Fixed 26-slot array, single decrement pass
The insight: with a known tiny alphabet you donβt need a hash map at all. Count the magazine into a length-26 array, then spend from it while reading the note; the first letter that would go negative proves failure. One array, no second counter.
class Solution:
def canConstruct(self, ransomNote: str, magazine: str) -> bool:
counts = [0] * 26
base = ord("a")
for ch in magazine:
counts[ord(ch) - base] += 1
for ch in ransomNote:
i = ord(ch) - base
counts[i] -= 1
if counts[i] < 0:
return False
return True
Walkthrough on "aab" vs "baa": after counting the magazine, slot a = 2, slot b = 1. Spending the note: a β a becomes 1; a β a becomes 0; b β b becomes 0. Nothing went negative β True.
Complexity: O(m + n) time, O(1) space (exactly 26 integers). Same asymptotics as the Counter, but constant-factor faster and a classic interview refinement worth naming.
Common pitfalls
- Comparing sets of letters instead of counts β
"aa" vs "ab" shares the letter set {a} on the demand side but fails on multiplicity.
- Decrement-pass order matters: count the magazine first and spend with the note. Doing it backwards flips the inequality and accepts wrong answers.
- The tempting one-liner
all(ch in magazine for ch in ransomNote) ignores multiplicity entirely β and hides an O(nΒ·m) scan besides.
- Early-exit idea worth keeping: if
len(ransomNote) > len(magazine), the answer is False before any counting.
Pattern takeaway
βCan A be assembled from the letters of B?β is a multiset containment question: order never matters, only per-symbol counts. Hash-count both sides and compare β and when the alphabet is small and fixed, downgrade the hash map to a flat array indexed by symbol. The same skeleton solves anagram checks, permutation-in-string, and every βdo the letters suffice?β variant.