Ransom Note

Mastering Ransom Note | CodingInterview.net

Ransom Note: Mastering Frequency Counting

Welcome back to codinginterview.net. If you have been following our curriculum, you are getting comfortable with pointers and search spaces. Now, it is time to master one of the most foundational concepts in technical interviews: Hash Maps and Frequency Counting. The "Ransom Note" problem is a classic gauge of whether a candidate understands how to track and look up data in constant time. By the end of this guide, using arrays and hash maps as high-speed inventory systems will be second nature to you.

1. Understanding the Problem

You are given two strings: ransomNote and magazine. Your task is to determine if you can construct the ransomNote using the letters provided in the magazine. If you can, return True. If you cannot, return False.

The Core Constraint (The Trap):

Just like making a physical ransom note by cutting letters out of a magazine, each letter in the magazine can only be used once. If your ransom note requires three 'a's, but the magazine only has two, you must return False. Additionally, string manipulation (like deleting characters from a string) in most languages creates entirely new strings in memory, which can silently destroy your performance.

2. The Naive Approach: Search and Destroy

The most intuitive way to solve this is to iterate through each character in your ransomNote and search for it in the magazine string. If you find the character, you remove it from the magazine (to ensure it isn't used again) and move on to the next letter.

Trade-off Analysis

While this logic works, it is mathematically expensive:

  • Time Complexity: O(N * M) — For every character in the ransom note (N), you potentially scan the entire magazine (M). Furthermore, deleting a character from a string often takes O(M) time on its own, making this highly inefficient.
  • Space Complexity: O(M) — Because strings are immutable in languages like Python and Java, "removing" a character actually means creating a brand-new string in memory every single time.

3. The Optimal Approach: The Frequency Array (O(N + M) Time)

To pass a senior-level technical interview, we must avoid nested loops and string recreation. We can achieve this by treating the magazine as an inventory. We will use a Frequency Map (or better yet, a simple fixed-size Array) to count exactly how many of each letter we have available.

Imagine running a bakery. Before you bake a large order (the ransom note), you first go to your pantry (the magazine) and count all your ingredients on a clipboard (the array). Then, you look at your recipe. For each ingredient required, you cross one off the clipboard. If you ever need an ingredient but the clipboard says you have 0 left, you immediately know the order cannot be completed.

Since the problem typically guarantees that both strings only contain lowercase English letters, we don't even need a heavy Hash Map. A simple array of size 26 (representing 'a' through 'z') is perfectly sufficient and lightning fast.

4. The Logic Step-by-Step

  1. Initial Check: If the ransomNote is longer than the magazine, it is physically impossible to construct. Return False immediately.
  2. Build the Inventory: Create an integer array of size 26, filled with zeros.
  3. Stock the Pantry: Iterate through every character in the magazine. Convert each character to its alphabetical index ('a' = 0, 'b' = 1, etc.) and increment the value at that index in your array.
  4. Consume the Inventory: Iterate through every character in the ransomNote. Convert each character to its index and decrement the value in your array.
    • The Check: If, after decrementing, the value at that index drops below 0, it means you needed a letter you didn't have. Return False immediately.
  5. If you successfully iterate through the entire ransomNote without returning false, you have all the required letters. Return True.

5. Complexity Analysis

  • Time Complexity: O(N + M) — We iterate through the magazine string (M) once, and the ransomNote string (N) once. Array lookups take O(1) time. This gives us a highly optimal linear time complexity.
  • Space Complexity: O(1) — We use an array of exactly 26 integers. Because this size never grows regardless of how large the input strings are, the space complexity is strictly constant.

6. Code Implementations

Expand the sections below to see the optimal O(1) space "Frequency Array" implementations across different languages.

View Python Solution
class Solution:
    def canConstruct(self, ransomNote: str, magazine: str) -> bool:
        if len(ransomNote) > len(magazine):
            return False
            
        # Initialize an array of 26 zeros for 'a' through 'z'
        char_counts = [0] * 26
        
        # Stock the inventory
        for char in magazine:
            index = ord(char) - ord('a')
            char_counts[index] += 1
            
        # Consume the inventory
        for char in ransomNote:
            index = ord(char) - ord('a')
            char_counts[index] -= 1
            
            # If we used a letter we don't have, return False
            if char_counts[index] < 0:
                return False
                
        return True
View Java Solution
class Solution {
    public boolean canConstruct(String ransomNote, String magazine) {
        if (ransomNote.length() > magazine.length()) {
            return false;
        }
        
        // Array to store counts of 26 lowercase English letters
        int[] charCounts = new int[26];
        
        // Stock the inventory from the magazine
        for (char c : magazine.toCharArray()) {
            charCounts[c - 'a']++;
        }
        
        // Consume the inventory using the ransom note
        for (char c : ransomNote.toCharArray()) {
            charCounts[c - 'a']--;
            
            // If the count drops below zero, we are missing a letter
            if (charCounts[c - 'a'] < 0) {
                return false;
            }
        }
        
        return true;
    }
}
View C++ Solution
#include <string>
#include <vector>

class Solution {
public:
    bool canConstruct(std::string ransomNote, std::string magazine) {
        if (ransomNote.length() > magazine.length()) {
            return false;
        }
        
        // Initialize vector of 26 elements with 0
        std::vector<int> charCounts(26, 0);
        
        // Build the frequency map
        for (char c : magazine) {
            charCounts[c - 'a']++;
        }
        
        // Check against the ransom note
        for (char c : ransomNote) {
            charCounts[c - 'a']--;
            
            if (charCounts[c - 'a'] < 0) {
                return false;
            }
        }
        
        return true;
    }
};

7. Conclusion: You Are Ready

Congratulations, you have just mastered the Frequency Counting pattern! While a standard Hash Map (like a dictionary in Python) is a great tool, recognizing that a limited character set (like 26 English letters) allows you to use a fixed-size array is a powerful optimization that impresses interviewers. It proves you understand the underlying mechanics of memory management and algorithmic overhead. Keep this "inventory clipboard" mental model in your toolkit, as it is the exact same logic used to solve complex problems like Anagrams and Sliding Window string questions!