LeetCode #383 Easy

Ransom Note

Ransom Note is LeetCode 383 (Easy). You get two strings, ransomNote and magazine. Return true if the note can be assembled from letters cut out of the magazine, and false otherwise.

  • Each letter in magazine can be used at most once. Two copies of a letter in the note need two copies in the magazine.
  • Order does not matter: the letters can be taken from anywhere in the magazine.
  • The magazine may contain extra letters the note never uses.
  • Both strings contain only lowercase English letters, and each is up to 10⁵ characters long, so the check should be linear, and a fixed table of 26 counts is enough.
Constraints
  • 1 <= ransomNote.length, magazine.length <= 10⁵
  • ransomNote and magazine consist of lowercase English letters
hash tablestringcounting
Open on LeetCode ↗
02

Intuition

This question is about quantities, not presence. Picture each magazine letter as a physical cut-out: once it is glued into the note it is gone, so the magazine is a stock of copies per letter, and the note spends that stock.

The note is buildable exactly when no letter is spent more often than it is stocked. Keeping one count per letter, adding for the magazine and subtracting for the note, checks that for all letters at once in a single pass over each string.

How to spot this pattern

"Can collection A be assembled from collection B, using each item of B once, in any order?" is a multiset-containment question: count B, then subtract A and watch for a count going below zero. Valid Anagram (242) is the stricter sibling, where the counts must match exactly rather than merely cover.

03

Approach

Try it first

Before reading on, find the shortest note and magazine where "every note letter appears somewhere in the magazine" is true but the answer is still false. Then aim for O(m + n) time with constant extra space.

1

Reject a note that is too long

Return false at once when len(ransomNote) > len(magazine). Each note letter consumes one magazine letter, so a longer note can never fit, and this O(1) check skips both loops on hopeless inputs.

2

Count the magazine

Make stock = [0] * 26 and add 1 at ord(ch) - ord('a') for every magazine letter. A plain array works because the input is only lowercase letters, and after this pass stock[k] is the number of copies of letter k you can cut out.

3

Spend letters for the note

Walk the note and, for each letter:

  • stock[k] == 0 – no copy is left, so return false immediately.
  • otherwise – decrement stock[k]; that copy is glued in and cannot be reused.

If the loop finishes, every letter found a copy, so return true.

4

Why counting letters is enough

After each note letter, stock[k] equals magazine copies minus note copies seen so far. It is 0 at the moment of use only when the note asks for more of that letter than the magazine holds, so the loop fails on exactly the impossible notes.

04

Ransom Note solution in Python | C++ | Java

▶1class Solution:
▶2 def canConstruct(self, ransomNote: str, magazine: str) -> bool:
▶3 if len(ransomNote) > len(magazine):
▶4 return False
▶5 stock = [0] * 26
▶6 for ch in magazine:
▶7 stock[ord(ch) - ord("a")] += 1
▶8 for ch in ransomNote:
▶9 k = ord(ch) - ord("a")
▶10 if stock[k] == 0:
▶11 return False
▶12 stock[k] -= 1
▶13 return True
magazinebacanoteaabstocka0 leftb0 leftc0 leftlengths pass: start counting the magazine
len(note)3≤ 4, so it may fit
stockall 026 slots, one per letter
Idea. Each magazine letter can be used once, so what matters is how many copies of each letter exist, not just whether it appears. Count the supply first, then let the note spend it.
magazinebacanoteaabstocka0 leftb1 leftc0 leftcount magazine letter 'b'
magazine[0]'b'
stock['b']1copies of 'b' so far
First b in the magazine: its column gets one block. Each block is one copy the note may cut out later.
magazinebacanoteaabstocka1 leftb1 leftc0 leftcount magazine letter 'a'
magazine[1]'a'
stock['a']1copies of 'a' so far
First a in the magazine: its column gets one block. Each block is one copy the note may cut out later.
magazinebacanoteaabstocka1 leftb1 leftc1 leftcount magazine letter 'c'
magazine[2]'c'
stock['c']1copies of 'c' so far
First c in the magazine: its column gets one block. Each block is one copy the note may cut out later.
magazinebacanoteaabstocka2 leftb1 leftc1 leftcount magazine letter 'a'
magazine[3]'a'
stock['a']2copies of 'a' so far
Another a: the column grows to 2. A set would have stored a once and lost this second copy, which is exactly the information the note may need.
magazinebacanoteaabstocka1 leftb1 leftc1 leftspend one 'a'
ransomNote[0]'a'
stock['a']1after spending one
The count for a was above 0, so a copy exists. Decrement it: that copy is now pasted into the note and cannot be reused; 1 still left for later.
magazinebacanoteaabstocka0 leftb1 leftc1 leftspend one 'a'
ransomNote[1]'a'
stock['a']0after spending one
The count for a was above 0, so a copy exists. Decrement it: that copy is now pasted into the note and cannot be reused, and the column is empty now.
magazinebacanoteaabstocka0 leftb0 leftc1 leftspend one 'b'
ransomNote[2]'b'
stock['b']0after spending one
The count for b was above 0, so a copy exists. Decrement it: that copy is now pasted into the note and cannot be reused, and the column is empty now.
magazinebacanoteaabstocka0 leftb0 leftc1 leftevery note letter found a copy: return true
stockc:1unused leftovers are fine
resulttrue
Buildable. No letter ever hit 0 before it was needed. The solid blocks still standing are magazine letters the note did not use; surplus is allowed, so they do not affect the answer.
05

Common pitfalls

Checking membership instead of counts

✗ Wrong
for ch in ransomNote:
    if ch not in magazine:
        return False
return True
✓ Right
if stock[k] == 0:
    return False
stock[k] -= 1

"aa" against "ab" passes the membership test twice using the same single a. It is also O(m·n), because in on a string is a linear scan.

Checking the count but never decrementing

✗ Wrong
if stock[k] == 0:
    return False
✓ Right
if stock[k] == 0:
    return False
stock[k] -= 1

Without the decrement the count never falls, so one copy satisfies every repeat of that letter. This is the membership bug again in a different form.

Removing characters from the magazine string

✗ Wrong
for ch in ransomNote:
    if ch not in magazine:
        return False
    magazine = magazine.replace(ch, "", 1)
✓ Right
stock = [0] * 26
for ch in magazine:
    stock[ord(ch) - ord("a")] += 1

It gives the right answer, but each replace copies the whole string, so the run is O(m·n). With 10⁵ characters on each side that is far too slow; counting is O(m + n).

06

Complexity

Time
O(m + n)
Space
O(1)
One pass over the magazine (length m) and at most one over the note (length n). The count table always has 26 slots, whatever the input size.
07

LeetCode 383 vs the HackerRank ransom note

HackerRank's version ("Hash Tables: Ransom Note") has the same story, but it counts whole words, which changes the data structure.

LeetCode 383HackerRank
Unitsingle lowercase lettersspace-separated words, case-sensitive
Counterarray of 26 intshash map from word to count
Outputreturn true / falseprint Yes / No
Ideacount supply, spend demandsame: count supply, spend demand
08

Ransom Note FAQ

What is the shortest Python solution?

return not Counter(ransomNote) - Counter(magazine). Subtracting Counters drops counts that fall to zero or below, so anything left is a letter the note needs more of than the magazine has. It is O(m + n) too, but always scans both strings fully, while the counting loop can stop at the first shortage.

Why use an array of 26 instead of a dictionary?

The input is limited to lowercase English letters, so ord(ch) - ord('a') is a perfect index from 0 to 25. An array avoids hashing and gives the same O(1) space. If the input could be any Unicode text, a dictionary would be the right choice.

Can you count the note and walk the magazine instead?

Yes: count what the note needs, decrement as matching magazine letters arrive, and return true once every need is met. That can stop early when the note is covered, but it has no early exit for failure, so an impossible note always costs a full magazine scan.