Start learning free

LESSON 264 OF 414 · HASH TABLE

Ransom Note in Rust: O(n + m) Letter Counts

Practice O(n + m) Read the full explanation

Given two strings ransomNote and magazine, return true if ransomNote can be constructed from magazine and false otherwise.

Each letter in magazine can only be used once in ransomNote.

Example 1:

Input:  ransomNote = "aa", magazine = "abb"
Output:  false

Example 2:

Input:  ransomNote = "aa", magazine = "aab"
Output:  true

Note:

Your algorithm should run in O(n + m) time and use O(Σ) extra space.


Hints:

Let's take the two examples given in the statement and solve them "by hand". In both examples, ransomNote consists of two 'a's. Which means we need to have at least two 'a's in magazine to be able to construct the solution. In the first example, we have only one 'a' in magazine and that's why we return false while in the second example, we have two of them, so that's why we return true. Pretty simple. Now what if ransomNote also contains letter 'b'? What property should be satisfied to return true? Take some small examples on paper and try to figure it out.

Let's take ransomNote = "abba". We need at least two 'a's and two 'b's in magazine to be able to construct the solution. First observation is that the order of letters in ransomNote is not important. The requirement is the same also for ransomNote = "aabb" or ransomNote = "abba". The same holds true for magazine. It doesn't matter if magazine = "aaabbb" or magazine = "ababab", both are good as the conditions are satisfied:
  • 'a' occurs in magazine at least the same number of times it does in ransomNote (3 >= 2)
  • 'b' occurs in magazine at least the same number of times it does in ransomNote (3 >= 2)
All these being said, can you figure out what condition should be satisfied to return true for the general case?

The condition is that for each letter of the alphabet (from 'a' to 'z'), the letter should appear in magazine at least the same number of times it does in ransomNote. So we need to separately count the number of occurrences of each letter in magazine and in ransomNote. How can we achieve this?

We have a data structure specially designed to do that: a frequency table, such as a Hash Map (or, since the letters are only 'a' to 'z', a 26-slot array). We can build two frequency tables:
  • one for ransomNote, where the entry for letter c is the number of occurrences of c in ransomNote
  • one for magazine, where the entry for letter c is the number of occurrences of c in magazine
How should we populate these tables?

Using a loop, we will go through every character c of ransomNote and increment the entry for c in its table. We will do the same for magazine. What should we do after we have populated these tables?

Using a loop, we should go through each character c of the alphabet (from 'a' to 'z') and check if the count of c in ransomNote is greater than its count in magazine. If so, we should return false. When this loop terminates, we can conclude that the solution is valid, so we can return true

STEP 1 OF 8
    Ctrl + Enter to check

    RUST · HASH TABLE

    Ransom Note in Rust, explained

    Problem overview

    The function can_construct receives two strings, ransom_note and magazine. It returns true when the note can be built from letters of the magazine, where each magazine letter can be used at most once, and false otherwise. The order of the letters doesn't matter, only how many of each there are.

    With ransom_note = "aa" and magazine = "abb" the magazine holds a single a, but the note needs two, so the answer is false. With magazine = "aab" there are two as, so the answer is true. The extra b is simply left unused.

    The lesson asks for O(n + m) time, where n and m are the two lengths, and extra space that depends only on the size of the alphabet, not on the input.

    Approach: count letters, then spend them

    A brute-force version would take each letter of the note and search the magazine for an unused copy, which costs O(n * m). The faster idea is to count first. The note can be built exactly when, for every letter, the magazine has at least as many copies as the note needs.

    That suggests a frequency table: how many times each letter occurs. A hash map would work, but the letters here are lowercase English ones, so there are only 26 possible keys. A fixed array of 26 counters is simpler and faster: slot 0 is a, slot 1 is b, and so on. Its size is a constant, which meets the space limit.

    The solution uses one table instead of two. It first adds every letter of the magazine to the table, so each slot says how many copies are available. Then it removes the letters the note needs one by one. If any slot goes below zero, the magazine ran out of that letter.

    On ransom_note = "aa" and magazine = "abb": after the first loop the table holds a: 1, b: 2. The note's first a brings slot 0 to 0, which is fine. The second a brings it to -1, so the function returns false immediately.

    Step by step

    1. Create the table: an array of 26 zeros of type i32, declared mut because the counts change.
    2. Loop over the magazine's bytes, with the loop variable c.
    3. Map each byte to a slot. Subtracting b'a' turns 'a'..='z' into 0..=25, and the cast to usize makes it usable as an index. Add one to that slot.
    4. Loop over the note's bytes the same way.
    5. Compute the index idx for the current letter.
    6. Spend one copy by decrementing counts[idx].
    7. Check for a shortage. A negative count means the note used more of this letter than the magazine supplied, so return false at once.
    8. Return true after the loop. Reaching the end means no letter ran short.

    The solution in Rust

    impl Solution {
        fn can_construct(ransom_note: String, magazine: String) -> bool {
            let mut counts = [0i32; 26];
            for c in magazine.bytes() {
                counts[(c - b'a') as usize] += 1;
            }
            for c in ransom_note.bytes() {
                let idx = (c - b'a') as usize;
                counts[idx] -= 1;
                if counts[idx] < 0 {
                    return false;
                }
            }
            true
        }
    }
    

    Rust details worth knowing:

    • Array repetition. [0i32; 26] builds an array of 26 copies of 0i32. The length is part of the type and fixed at compile time, and the array lives on the stack.
    • Bytes, not characters. bytes() yields each u8 of the string's UTF-8 encoding. For lowercase ASCII letters, one byte is one letter. chars() would also work, but bytes() gives numbers that are easy to subtract.
    • Byte literals. b'a' is a u8 with the value 97. Both c and b'a' are u8, so the subtraction is a u8 subtraction, and the result is converted with as usize.
    • Bounds-checked indexing. counts[idx] panics if idx is 26 or more, instead of reading unrelated memory.
    • The final expression. true without a semicolon is the value of the function.

    Complexity

    Time: O(n + m). The first loop visits each of the m magazine bytes once, and the second visits each of the n note bytes at most once. Each step is a constant-time array update.

    Space: O(1), or O(Σ) where Σ is the alphabet size. The table has 26 slots no matter how long the strings are.

    Edge cases and common mistakes

    • Empty note. Nothing needs to be built, so the second loop never runs and the answer is true, even for an empty magazine.
    • Empty magazine, non-empty note. The first letter of the note brings its slot to -1, so the answer is false.
    • Magazine shorter than the note. This can never succeed, and the loop finds out by the time it runs out of copies.
    • Checking only that letters exist. Seeing each note letter somewhere in the magazine isn't enough: "aa" against "abb" has an a but only one. The counts are what matter.
    • Letters outside a to z. The table only has room for 26 lowercase letters. For an uppercase A, the byte 65 minus 97 doesn't go negative: in the optimized build the lessons use, the u8 subtraction wraps to 224, and indexing slot 224 panics with an out-of-bounds error. In a debug build the subtraction itself panics. Code that must accept other characters needs a bigger table (256 slots indexed by the byte) or a HashMap.
    • Counting the note first. That works as well, with the check done in a final pass over the table, but the version above can stop early.

    Testing

    • ransom_note = "aa", magazine = "abb" gives false.
    • ransom_note = "aa", magazine = "aab" gives true.
    • ransom_note = "abba", magazine = "ababab" gives true.
    • ransom_note = "", magazine = "abc" gives true.
    • ransom_note = "a", magazine = "" gives false.
    Keep going with RustAbout 340 step-by-step lessons, from your first line of code to coding interviews. Free to start.
    Start learning free