AlgoMaster Logo

Replace Words

mediumFrequencyUpdated September 21, 2026

Understanding the Problem

We are given a list of "root" words and a sentence. For every word in the sentence, we need to check if any root is a prefix of that word. If so, we replace the word with the shortest such root. If no root is a prefix, the word stays as-is.

The shortest-root requirement matters. If the dictionary contains both "ca" and "cat", and we encounter "cattle," the answer is "ca" because it is shorter. We cannot stop at any matching root; we need the one with the smallest length.

So the problem reduces to: for each word, find the shortest prefix that exists in the dictionary. This is a prefix-matching problem, which is what hash sets and tries are good at.

Key Constraints:

  • dictionary.length <= 1000 and dictionary[i].length <= 100 -> The total characters across all roots is at most 100,000, so a Trie has at most that many nodes.
  • sentence.length <= 10^6 -> The sentence can be large, so we want per-word work that does not depend on dictionary size.
  • Number of words <= 1000 and word length <= 1000 -> Per word, both a hash-set prefix scan and a Trie walk do at most 1000 steps.

Approach 1: Brute Force (Check All Roots)

Intuition

For each word in the sentence, loop through every root in the dictionary and check if that root is a prefix of the word. Among all matching roots, pick the shortest one. If no root matches, keep the word as-is.

This needs no extra data structures, only string comparison.

Algorithm

  1. Split the sentence into individual words.
  2. For each word, iterate through all roots in the dictionary.
  3. For each root, check if the word starts with that root.
  4. Track the shortest matching root. If a match is found and it is shorter than the current best, update the best.
  5. Replace the word with the shortest matching root (or keep the original if no root matched).
  6. Join all processed words back into a sentence with spaces.

Visualization and Code

Loading animation...

This scans the entire dictionary for every word. The next approach instead generates each word's prefixes and looks them up in a set, stopping at the first match.

Approach 2: Hash Set with Prefix Checking

Intuition

Store all roots in a hash set for O(1) average lookup. Then for each word, generate its prefixes in increasing length order: length 1, length 2, length 3, and so on. The first prefix found in the set is the shortest matching root, so we stop there.

The word length k is at most 1000, so a word produces at most k prefix lookups, independent of dictionary size.

Algorithm

  1. Add all roots from the dictionary into a hash set.
  2. Split the sentence into words.
  3. For each word, iterate through prefix lengths from 1 to the word's length.
  4. At each length, extract the prefix and check if it exists in the hash set.
  5. If found, replace the word with this prefix and stop checking longer prefixes.
  6. If no prefix matches, keep the original word.
  7. Join all words back with spaces.

Visualization and Code

Loading animation...

The hash set approach allocates a substring object for every prefix it checks. A Trie removes that cost: it walks the word one character at a time along a path shared by all roots, testing every root at once.

Approach 3: Trie (Optimal)

Intuition

We build a Trie from all the roots in the dictionary. Then for each word in the sentence, we walk the Trie character by character. As soon as we reach a node marked as the end of a root, we have the shortest matching root. There is no substring allocation and no per-prefix hash computation.

One optimization applies during insertion: if we reach a node already marked as the end of a shorter root, we stop inserting the current root. Once "ca" is in the Trie, inserting "cat" past the "ca" node is wasted work, because a word starting with "cat" also starts with "ca", and "ca" is shorter, so "ca" is always the answer.

Algorithm

  1. Build a Trie from all roots in the dictionary. Each node has up to 26 children (lowercase letters only) and a boolean flag indicating if it marks the end of a root.
  2. Split the sentence into words.
  3. For each word, traverse the Trie starting from the root node. For each character:
    • If the current Trie node is marked as the end of a root, return the prefix up to this point.
    • If the current character does not have a child in the Trie, stop. No root is a prefix.
    • Otherwise, move to the child node and continue.
  4. If we exhaust the word without finding a root, keep the original word.
  5. Join all processed words with spaces.

Visualization and Code

Loading animation...