class Solution:
def replaceWords(self, dictionary, sentence):
trie = {}
for root in dictionary:
node = trie
for char in root:
if char not in node:
node[char] = {}
node = node[char]
node['#'] = True # End of root marker
def replace(word):
node = trie
prefix = ''
for char in word:
if char not in node:
break
prefix += char
node = node[char]
if '#' in node:
return prefix
return word
return ' '.join(replace(word) for word in sentence.split())
class Solution {
public:
struct TrieNode {
bool isEnd;
unordered_map<char, TrieNode*> children;
TrieNode() : isEnd(false) {}
};
void insert(TrieNode* root, const string& word) {
TrieNode* node = root;
for (char c : word) {
if (!node->children.count(c))
node->children[c] = new TrieNode();
node = node->children[c];
}
node->isEnd = true;
}
string replace(const string& word, TrieNode* root) {
TrieNode* node = root;
string prefix;
for (char c : word) {
if (!node->children.count(c))
break;
prefix += c;
node = node->children[c];
if (node->isEnd)
return prefix;
}
return word;
}
string replaceWords(vector<string>& dictionary, string sentence) {
TrieNode* root = new TrieNode();
for (const string& word : dictionary)
insert(root, word);
istringstream iss(sentence);
string word, result;
while (iss >> word) {
if (!result.empty()) result += " ";
result += replace(word, root);
}
return result;
}
};
class Solution {
static class TrieNode {
boolean isEnd;
Map<Character, TrieNode> children = new HashMap<>();
}
public String replaceWords(List<String> dictionary, String sentence) {
TrieNode root = new TrieNode();
for (String word : dictionary) {
TrieNode node = root;
for (char c : word.toCharArray()) {
node = node.children.computeIfAbsent(c, k -> new TrieNode());
}
node.isEnd = true;
}
StringBuilder sb = new StringBuilder();
for (String word : sentence.split(" ")) {
TrieNode node = root;
StringBuilder prefix = new StringBuilder();
boolean replaced = false;
for (char c : word.toCharArray()) {
if (!node.children.containsKey(c) || node.isEnd) break;
prefix.append(c);
node = node.children.get(c);
if (node.isEnd) {
replaced = true;
break;
}
}
if (sb.length() > 0) sb.append(" ");
sb.append((node.isEnd) ? prefix.toString() : word);
}
return sb.toString();
}
}
class TrieNode {
constructor() {
this.children = {};
this.isEnd = false;
}
}
function buildTrie(dictionary) {
const root = new TrieNode();
for (const word of dictionary) {
let node = root;
for (const char of word) {
if (!node.children[char]) node.children[char] = new TrieNode();
node = node.children[char];
}
node.isEnd = true;
}
return root;
}
function replaceWords(dictionary, sentence) {
const root = buildTrie(dictionary);
function replace(word) {
let node = root;
let prefix = '';
for (const char of word) {
if (!node.children[char]) break;
prefix += char;
node = node.children[char];
if (node.isEnd) return prefix;
}
return word;
}
return sentence.split(' ').map(replace).join(' ');
}
You are given a list of dictionary
words, each considered as a "root". You are also given a sentence
consisting of words separated by spaces.
The task is to replace all words in the sentence
that have a root in the dictionary
as a prefix with that root word. If a word in the sentence has multiple roots that can match as a prefix, you must replace it with the shortest such root.
Constraints:
dictionary
is a non-empty string of lowercase English letters.sentence
is also a non-empty string of lowercase English letters.
To solve this problem, let's first consider a naive or brute-force approach. For each word in the sentence
, we could check every root in the dictionary
to see if it is a prefix, and if so, replace the word with the shortest such root. This would involve a lot of repeated prefix checks, especially if the dictionary or sentence is large.
However, this brute-force method is inefficient. To optimize, we can leverage a data structure that supports fast prefix lookups: a Trie (prefix tree). By storing all roots in a Trie, we can efficiently determine the shortest matching root for any word by traversing the Trie character by character until we either find a matching root or determine that none exists.
This approach is both faster and more elegant, as it avoids redundant checks and leverages the hierarchical nature of prefixes.
Here is a step-by-step explanation of the optimized solution using a Trie:
dictionary
, insert it into the Trie character by character.sentence
into individual words.prefix
string.prefix
(the root).Using a Trie ensures that finding the shortest root is efficient, because we stop at the first matching root during traversal.
Sample Input:
dictionary = ["cat", "bat", "rat"]
sentence = "the cattle was rattled by the battery"
Step-by-step:
"the cat was rat by the bat"
At each step, the algorithm efficiently finds the shortest root using the Trie structure and replaces the word accordingly.
Brute-force Approach:
The Trie approach is significantly faster, especially when the dictionary is large or words are long, as it avoids redundant prefix checks.
In this problem, we efficiently replace words in a sentence with the shortest matching root from a dictionary by using a Trie (prefix tree). This data structure allows for quick prefix checks, making the solution much more efficient than brute-force approaches. The key insight is to stop the search as soon as the shortest matching root is found, ensuring both correctness and optimal performance.