LeetCodee

648. Replace Words

Jump to Solution: Python Java C++ JavaScript C#

Problem Description

In English, we have a concept called root, which can be followed by some other word to form another longer word - let's call this word successor. For example, when the root "an" is followed by the successor word "other", we can form a new word "another".

Given a dictionary consisting of many roots and a sentence consisting of words separated by spaces, replace all the successors in the sentence with the root forming it. If a successor can be replaced by more than one root, replace it with the root that has the shortest length.

Return the sentence after the replacement.

Examples:

Example 1:

Input: dictionary = ["cat","bat","rat"], sentence = "the cattle was rattled by the battery"
Output: "the cat was rat by the bat"

Example 2:

Input: dictionary = ["a","b","c"], sentence = "aadsfasf absbs bbab cadsfafs"
Output: "a a b c"

Constraints:

  • 1 ≤ dictionary.length ≤ 1000
  • 1 ≤ dictionary[i].length ≤ 100
  • dictionary[i] consists of only lower-case letters.
  • 1 ≤ sentence.length ≤ 10^6
  • sentence consists of only lower-case letters and spaces.
  • The number of words in sentence is in the range [1, 1000]
  • The length of each word in sentence is in the range [1, 1000]
  • Every two consecutive words in sentence will be separated by exactly one space.
  • sentence does not have leading or trailing spaces.

Python Solution

class Solution:
    def replaceWords(self, dictionary: List[str], sentence: str) -> str:
        # Build trie
        root = {}
        for word in dictionary:
            node = root
            for char in word:
                node = node.setdefault(char, {})
            node['$'] = word  # Mark end of word
        
        def find_root(word):
            node = root
            for i, char in enumerate(word):
                if '$' in node:  # Found a root
                    return node['$']
                if char not in node:  # No root exists
                    return word
                node = node[char]
            return word
        
        # Process each word in the sentence
        words = sentence.split()
        return ' '.join(find_root(word) for word in words)

Alternative Solution (Using Set):

class Solution:
    def replaceWords(self, dictionary: List[str], sentence: str) -> str:
        rootset = set(dictionary)
        
        def replace(word):
            for i in range(1, len(word) + 1):
                if word[:i] in rootset:
                    return word[:i]
            return word
        
        return ' '.join(map(replace, sentence.split()))

Implementation Notes:

  • First solution uses Trie for efficient prefix matching
  • Second solution uses Set for simpler implementation
  • Trie solution has better time complexity for long words

Java Solution

class Solution {
    class TrieNode {
        Map children = new HashMap<>();
        String word = null;
    }
    
    public String replaceWords(List dictionary, String sentence) {
        TrieNode root = new TrieNode();
        
        // Build trie
        for (String word : dictionary) {
            TrieNode node = root;
            for (char c : word.toCharArray()) {
                node.children.putIfAbsent(c, new TrieNode());
                node = node.children.get(c);
            }
            node.word = word;
        }
        
        // Process sentence
        StringBuilder result = new StringBuilder();
        String[] words = sentence.split(" ");
        
        for (String word : words) {
            if (result.length() > 0) {
                result.append(" ");
            }
            
            TrieNode node = root;
            for (char c : word.toCharArray()) {
                if (node.word != null || !node.children.containsKey(c)) {
                    break;
                }
                node = node.children.get(c);
            }
            result.append(node.word != null ? node.word : word);
        }
        
        return result.toString();
    }
}

C++ Solution

class Solution {
    struct TrieNode {
        unordered_map children;
        string* word = nullptr;
        
        ~TrieNode() {
            for (auto& pair : children) {
                delete pair.second;
            }
        }
    };
    
public:
    string replaceWords(vector& dictionary, string sentence) {
        TrieNode* root = new TrieNode();
        
        // Build trie
        for (const string& word : dictionary) {
            TrieNode* node = root;
            for (char c : word) {
                if (!node->children[c]) {
                    node->children[c] = new TrieNode();
                }
                node = node->children[c];
            }
            node->word = &word;
        }
        
        // Process sentence
        stringstream ss(sentence);
        string word, result;
        bool first = true;
        
        while (ss >> word) {
            if (!first) {
                result += " ";
            }
            first = false;
            
            TrieNode* node = root;
            for (char c : word) {
                if (node->word || !node->children[c]) {
                    break;
                }
                node = node->children[c];
            }
            result += node->word ? *node->word : word;
        }
        
        delete root;
        return result;
    }
};

JavaScript Solution

/**
 * @param {string[]} dictionary
 * @param {string} sentence
 * @return {string}
 */
var replaceWords = function(dictionary, sentence) {
    const root = {};
    
    // Build trie
    for (const word of dictionary) {
        let node = root;
        for (const char of word) {
            node = node[char] = node[char] || {};
        }
        node.word = word;
    }
    
    // Find root word
    const findRoot = (word) => {
        let node = root;
        for (const char of word) {
            if (node.word) return node.word;
            if (!node[char]) return word;
            node = node[char];
        }
        return word;
    };
    
    // Process sentence
    return sentence.split(' ')
                  .map(findRoot)
                  .join(' ');
};

C# Solution

public class Solution {
    class TrieNode {
        public Dictionary Children { get; } = new Dictionary();
        public string Word { get; set; }
    }
    
    public string ReplaceWords(IList dictionary, string sentence) {
        var root = new TrieNode();
        
        // Build trie
        foreach (var word in dictionary) {
            var node = root;
            foreach (var c in word) {
                if (!node.Children.ContainsKey(c)) {
                    node.Children[c] = new TrieNode();
                }
                node = node.Children[c];
            }
            node.Word = word;
        }
        
        // Process sentence
        return string.Join(" ", 
            sentence.Split(' ')
                   .Select(word => {
                       var node = root;
                       foreach (var c in word) {
                           if (node.Word != null || !node.Children.ContainsKey(c)) {
                               break;
                           }
                           node = node.Children[c];
                       }
                       return node.Word ?? word;
                   }));
    }
}

Alternative Solution (Using HashSet):

public class Solution {
    public string ReplaceWords(IList dictionary, string sentence) {
        var rootSet = new HashSet(dictionary);
        
        return string.Join(" ",
            sentence.Split(' ')
                   .Select(word => {
                       for (int i = 1; i <= word.Length; i++) {
                           var prefix = word.Substring(0, i);
                           if (rootSet.Contains(prefix)) {
                               return prefix;
                           }
                       }
                       return word;
                   }));
    }
}

Implementation Notes:

  • First solution uses Trie for efficient prefix matching
  • Second solution uses HashSet for simpler implementation
  • Both solutions handle all edge cases correctly