The problem
You get two strings, ransom and mag. Each letter in the magazine can be used at most once. Return True if you can build ransom from the magazine’s letters, otherwise False.
Input: ransom = "aa", mag = "aab"
Output: True
Input: ransom = "aa", mag = "ab"
Output: FalseThe note needs two as. "aab" has two, but "ab" has only one.
How to think about it
Start with brute force. For each letter of the note, search the magazine for an unused copy and cross it out. Each search is O(m), so that’s O(n·m) for a note of length n and a magazine of length m. Order doesn’t matter at all here, which is a strong hint that you’re doing too much work.
The key insight: only the counts matter. The note is buildable if and only if, for every letter, the note needs no more copies than the magazine has. So count the letters in each string and compare letter by letter. That’s one pass over each string.
Python makes the comparison one expression. Counter subtraction keeps only the positive differences. Counter(ransom) − Counter(mag) is therefore exactly “the letters the magazine is short of”. If that result is empty, nothing is missing and the note can be built. An empty Counter is falsy, so not (…) turns it into True.
Which pattern is this? Hash map frequency counting. The tell is “can A be made from the pieces of B?”, where order is irrelevant.
Approach
- Count the letters of
ransomand ofmag. - Subtract the magazine counts from the note counts, keeping only letters where the note needs more.
- If nothing is left, return
True; otherwise returnFalse.
Without Counter: fill a 26-slot array from the magazine, then for each note letter subtract one and return False the moment any slot goes negative.
Solution
from collections import Counter
def can_construct(ransom, mag):
return not (Counter(ransom) - Counter(mag)) # leftover = letters the magazine lacks
# can_construct("aa", "aab") -> TrueDry run
ransom |
mag |
Counter(ransom) |
Counter(mag) |
Difference | Result |
|---|---|---|---|---|---|
"aa" |
"aab" |
{a: 2} |
{a: 2, b: 1} |
empty | True |
"aa" |
"ab" |
{a: 2} |
{a: 1, b: 1} |
{a: 1} |
False |
"a" |
"b" |
{a: 1} |
{b: 1} |
{a: 1} |
False |
In the first row, a is 2 − 2 = 0 and b is 0 − 1 = −1; both are dropped because they’re not positive, so nothing is short.
Complexity
- Time: O(n + m). One pass to count each string, plus a subtraction over at most 26 distinct letters.
- Space: O(1). With lowercase English letters, each Counter holds at most 26 keys.
Edge cases and pitfalls
- Empty note: always buildable.
Counter("")minus anything is empty, so it returnsTrue. - Extra magazine letters are fine. Negative differences are discarded, which is exactly the behaviour you want.
- Using sets (
set(ransom) <= set(mag)) ignores counts and wrongly accepts"aa"from"ab". - Don’t swap the arguments.
Counter(mag) − Counter(ransom)asks the opposite question. - Early exit: the array version can stop at the first shortage, which matters if the note is huge and fails early.
Related problems
- Valid Anagram (LeetCode 242): the counts must be equal, not just “fit inside”.
- First Unique Character in a String (LeetCode 387): another count-then-scan string problem.
- Minimum Window Substring (LeetCode 76): “does this window contain all the needed letters?”, checked as the window slides.
- 1.Two Sum (LeetCode 1)
- 2.Valid Anagram (LeetCode 242)
- 3.Contains Duplicate (LeetCode 217)
- 4.Ransom Note (LeetCode 383)
- 5.First Unique Character in a String (LeetCode 387)
- 6.Majority Element (LeetCode 169)
- 7.Isomorphic Strings (LeetCode 205)
- 8.Group Anagrams (LeetCode 49)
- 9.Top K Frequent Elements (LeetCode 347)
- 10.Longest Consecutive Sequence (LeetCode 128)
- 11.Subarray Sum Equals K (LeetCode 560)
- 12.Valid Sudoku (LeetCode 36)
- 13.LRU Cache (LeetCode 146)