Facebook Pixel

383. Ransom Note

EasyHash TableStringCounting
Leetcode Link

Problem Description

You are given two strings: ransomNote and magazine. Your task is to determine whether you can construct the ransomNote using only the letters available in magazine.

The key constraints are:

  • Each letter in magazine can only be used once
  • You need to use the exact letters from magazine (not just check if the letters exist)
  • Return true if ransomNote can be constructed from magazine, otherwise return false

For example:

  • If ransomNote = "aa" and magazine = "aab", you can construct the ransom note using two 'a's from the magazine, so return true
  • If ransomNote = "aa" and magazine = "ab", you cannot construct the ransom note because the magazine only has one 'a', so return false

The solution uses a frequency counter to track the available letters in magazine. As we iterate through each character in ransomNote, we decrement the count for that character. If at any point a character's count becomes negative, it means we don't have enough of that letter in magazine to construct the ransomNote.

Quick Interview Experience
Help others by sharing your interview experience
Have you seen this problem before?

Intuition

Think of this problem like cutting letters from a physical magazine to create a ransom note. You have a limited supply of each letter in the magazine, and once you use a letter, it's gone.

The natural approach is to first take inventory of what letters we have available. We count how many of each letter appears in magazine - this becomes our "letter bank" or available resources.

Then, as we try to build the ransomNote, we go through it letter by letter. For each letter we need, we check our inventory and "use up" one instance of that letter by decreasing its count.

The key insight is that we don't need to build the actual ransom note - we just need to verify if we have enough letters. So we can simply track whether we ever run out of a needed letter. If at any point we need a letter but our count for it goes negative (meaning we've used more than what was available), we know it's impossible to construct the ransom note.

This is essentially a resource allocation problem: do we have enough resources (letters from magazine) to meet the demand (letters needed for ransomNote)? By tracking the availability and consumption of each letter, we can efficiently determine if construction is possible.

The beauty of this approach is that we can fail fast - as soon as we encounter a letter we don't have enough of, we can immediately return false without checking the rest of the ransom note.

Solution Approach

We use a hash table (or an array of size 26 for lowercase English letters) to track the frequency of each character in the magazine. In Python, we can use the Counter class from the collections module which automatically creates a frequency map.

Here's the step-by-step implementation:

  1. Create a frequency counter: First, we count all characters in magazine using Counter(magazine). This gives us a dictionary-like object where keys are characters and values are their frequencies.

  2. Iterate through ransomNote: For each character c in ransomNote:

    • Decrement the count of c in our counter by 1: cnt[c] -= 1
    • Check if the count becomes negative: if cnt[c] < 0
    • If negative, it means we've tried to use more instances of this character than available in the magazine, so return False
  3. Return True: If we successfully process all characters in ransomNote without any count going negative, we can construct the ransom note, so return True.

The time complexity is O(m + n) where m is the length of magazine and n is the length of ransomNote. We need to traverse the magazine once to build the frequency map and then traverse the ransom note once to verify availability.

The space complexity is O(1) or O(k) where k is the number of unique characters. Since we're dealing with lowercase English letters, the maximum unique characters is 26, making it constant space. If using a hash table for any Unicode characters, the space would depend on the number of unique characters in the magazine.

The algorithm efficiently handles the "each letter can only be used once" constraint by tracking exact counts and ensuring we never use more letters than available.

Ready to land your dream job?

Unlock your dream job with a 5-minute evaluator for a personalized learning plan!

Start Evaluator

Example Walkthrough

Let's walk through a concrete example with ransomNote = "aab" and magazine = "baa".

Step 1: Create frequency counter for magazine We count each character in magazine = "baa":

  • 'b': 1 occurrence
  • 'a': 2 occurrences

Our counter looks like: {'b': 1, 'a': 2}

Step 2: Process each character in ransomNote

Now we go through ransomNote = "aab" character by character:

First character: 'a'

  • Current counter: {'b': 1, 'a': 2}
  • Decrement 'a' count: 2 - 1 = 1
  • Updated counter: {'b': 1, 'a': 1}
  • Is count negative? No, continue

Second character: 'a'

  • Current counter: {'b': 1, 'a': 1}
  • Decrement 'a' count: 1 - 1 = 0
  • Updated counter: {'b': 1, 'a': 0}
  • Is count negative? No, continue

Third character: 'b'

  • Current counter: {'b': 1, 'a': 0}
  • Decrement 'b' count: 1 - 1 = 0
  • Updated counter: {'b': 0, 'a': 0}
  • Is count negative? No, continue

Step 3: Return result We successfully processed all characters without any count going negative, so we return True.

Counter-example: If ransomNote = "aab" and magazine = "ab":

  • Counter after magazine: {'a': 1, 'b': 1}
  • Process first 'a': counter becomes {'a': 0, 'b': 1}
  • Process second 'a': counter would become {'a': -1, 'b': 1}
  • Since 'a' count is negative, we immediately return False - we don't have enough 'a's in the magazine.

Solution Implementation

1from collections import Counter
2
3class Solution:
4    def canConstruct(self, ransomNote: str, magazine: str) -> bool:
5        # Count frequency of each character in magazine
6        char_count = Counter(magazine)
7      
8        # Check if each character in ransomNote can be constructed from magazine
9        for char in ransomNote:
10            # Decrement the count for current character
11            char_count[char] -= 1
12          
13            # If count becomes negative, magazine doesn't have enough of this character
14            if char_count[char] < 0:
15                return False
16      
17        # All characters in ransomNote can be constructed from magazine
18        return True
19
1class Solution {
2    /**
3     * Determines if a ransom note can be constructed from the letters in a magazine.
4     * Each letter in the magazine can only be used once.
5     * 
6     * @param ransomNote The string to be constructed
7     * @param magazine The string containing available letters
8     * @return true if ransomNote can be constructed from magazine, false otherwise
9     */
10    public boolean canConstruct(String ransomNote, String magazine) {
11        // Array to store frequency count of each letter (26 lowercase English letters)
12        int[] letterFrequency = new int[26];
13      
14        // Count the frequency of each letter in the magazine
15        for (int i = 0; i < magazine.length(); i++) {
16            char currentChar = magazine.charAt(i);
17            // Convert character to index (0-25) by subtracting 'a'
18            int charIndex = currentChar - 'a';
19            letterFrequency[charIndex]++;
20        }
21      
22        // Check if ransom note can be constructed by consuming letters from magazine
23        for (int i = 0; i < ransomNote.length(); i++) {
24            char currentChar = ransomNote.charAt(i);
25            // Convert character to index (0-25) by subtracting 'a'
26            int charIndex = currentChar - 'a';
27          
28            // Decrement the count for this letter and check if it goes negative
29            letterFrequency[charIndex]--;
30            if (letterFrequency[charIndex] < 0) {
31                // Not enough of this letter in magazine to construct ransom note
32                return false;
33            }
34        }
35      
36        // All letters in ransom note can be constructed from magazine
37        return true;
38    }
39}
40
1class Solution {
2public:
3    bool canConstruct(string ransomNote, string magazine) {
4        // Array to store frequency count of each letter (a-z)
5        // Index 0 represents 'a', index 1 represents 'b', and so on
6        int letterCount[26] = {0};
7      
8        // Count the frequency of each character in the magazine
9        for (char& ch : magazine) {
10            letterCount[ch - 'a']++;
11        }
12      
13        // Check if ransom note can be constructed from magazine letters
14        for (char& ch : ransomNote) {
15            // Decrement the count for current character
16            // If count becomes negative, magazine doesn't have enough of this letter
17            if (--letterCount[ch - 'a'] < 0) {
18                return false;
19            }
20        }
21      
22        // All characters in ransom note can be constructed from magazine
23        return true;
24    }
25};
26
1/**
2 * Determines if a ransom note can be constructed from the letters in a magazine.
3 * Each letter in the magazine can only be used once.
4 * 
5 * @param ransomNote - The string to be constructed
6 * @param magazine - The string containing available letters
7 * @returns true if the ransom note can be constructed, false otherwise
8 */
9function canConstruct(ransomNote: string, magazine: string): boolean {
10    // Create an array to count occurrences of each letter (26 letters in alphabet)
11    // Index 0 represents 'a', index 1 represents 'b', etc.
12    const letterCount: number[] = Array(26).fill(0);
13  
14    // Count each letter in the magazine
15    for (const character of magazine) {
16        // Convert character to array index (0-25) by subtracting ASCII value of 'a' (97)
17        const index: number = character.charCodeAt(0) - 97;
18        letterCount[index]++;
19    }
20  
21    // Check if ransom note can be constructed by consuming letters from magazine
22    for (const character of ransomNote) {
23        // Convert character to array index and decrement the count
24        const index: number = character.charCodeAt(0) - 97;
25        letterCount[index]--;
26      
27        // If count goes negative, this letter is not available in sufficient quantity
28        if (letterCount[index] < 0) {
29            return false;
30        }
31    }
32  
33    // All letters in ransom note were successfully matched
34    return true;
35}
36

Time and Space Complexity

The time complexity is O(m + n), where m is the length of ransomNote and n is the length of magazine. This is because:

  • Creating the Counter from magazine takes O(n) time as it needs to iterate through all characters in the magazine string
  • The for loop iterates through all characters in ransomNote, which takes O(m) time
  • Each operation inside the loop (dictionary access and comparison) takes O(1) time
  • Total: O(n) + O(m) = O(m + n)

The space complexity is O(C), where C is the size of the character set. In this problem, since we're dealing with lowercase English letters, C = 26. The Counter object stores at most C unique characters from the magazine string, regardless of how long the magazine string is. Even though the Counter is created from a string of length n, it only stores unique characters and their counts, which is bounded by the size of the alphabet.

Common Pitfalls

1. Checking Character Existence Instead of Count

A frequent mistake is to only check if characters from ransomNote exist in magazine without considering their frequencies. This leads to incorrect results when the ransom note requires multiple instances of the same character.

Incorrect approach:

def canConstruct(self, ransomNote: str, magazine: str) -> bool:
    for char in ransomNote:
        if char not in magazine:
            return False
    return True

This would incorrectly return True for ransomNote = "aa" and magazine = "ab" because it only checks if 'a' exists, not if there are enough 'a's.

Solution: Always track and decrement character counts to ensure each character is used only once.

2. Modifying the Original String

Some developers attempt to remove characters from the magazine string as they're used, which is inefficient and can lead to errors.

Inefficient approach:

def canConstruct(self, ransomNote: str, magazine: str) -> bool:
    magazine_list = list(magazine)
    for char in ransomNote:
        if char in magazine_list:
            magazine_list.remove(char)  # O(n) operation each time
        else:
            return False
    return True

This has O(n²) time complexity because remove() is O(n) and is called for each character in ransomNote.

Solution: Use a frequency counter which provides O(1) lookup and update operations.

3. Not Handling Empty Strings Properly

While the Counter approach handles empty strings correctly, manual implementations might not consider edge cases like empty ransomNote (should return True) or empty magazine with non-empty ransomNote (should return False).

Solution: The Counter approach naturally handles these cases, but if implementing manually, add explicit checks or ensure your logic covers these scenarios.

4. Using Counter Subtraction Incorrectly

Some might try to use Counter's subtraction operation but misunderstand how it works with negative counts.

Potentially confusing approach:

def canConstruct(self, ransomNote: str, magazine: str) -> bool:
    cnt_ransom = Counter(ransomNote)
    cnt_magazine = Counter(magazine)
    cnt_ransom.subtract(cnt_magazine)
    return all(count <= 0 for count in cnt_ransom.values())

While this works, it's less intuitive and requires understanding that subtract() keeps negative counts.

Solution: The presented approach of decrementing counts while iterating is clearer and provides early termination when a character is unavailable.

Discover Your Strengths and Weaknesses: Take Our 5-Minute Quiz to Tailor Your Study Plan:

Which of these pictures shows the visit order of a depth-first search?


Recommended Readings

Want a Structured Path to Master System Design Too? Don’t Miss This!

Load More