Given an array of strings words
, your task is to find the longest word in words
such that every prefix of the word is also in words
. A prefix of a word is any substring that starts at the beginning of the word and ends at any position before or at the end. For example, for the word "apple"
, the prefixes are "a"
, "ap"
, "app"
, "appl"
, and "apple"
.
If there are multiple answers with the same length, return the lexicographically smallest one. If there is no such word, return an empty string.
Constraints:
words
are unique.To solve this problem, we first need to recognize that the main challenge is checking, for each word, whether all of its prefixes are present in the input list. A brute-force approach would be to check every word and, for each, verify all its prefixes by scanning the list. However, this would be inefficient, especially for large lists or long words.
To optimize, we can use a data structure that allows for fast prefix lookups, such as a set (hash set). This way, we can check if a prefix exists in constant time. Next, we realize that we want the longest word with all prefixes present, and if there are ties, the lexicographically smallest.
We also notice that if we process words in a sorted order (by length, then lexicographically), we can efficiently track which words are "buildable" from their prefixes. This insight leads us toward a more efficient solution.
Let's break down the optimized solution step by step:
This approach is efficient because:
Input: words = ["w","wo","wor","worl","world","banana","ban","bana","banan"]
["w", "wo", "ban", "wor", "bana", "worl", "banan", "world", "banana"]
"world"
, since all its prefixes are present and it is the longest such word.
class Solution:
def longestWord(self, words):
words_set = set(words)
words.sort()
ans = ""
for word in sorted(words, key=lambda w: (len(w), w)):
valid = True
for i in range(1, len(word)):
if word[:i] not in words_set:
valid = False
break
if valid:
if len(word) > len(ans):
ans = word
return ans
class Solution {
public:
string longestWord(vector<string>& words) {
unordered_set<string> wordSet(words.begin(), words.end());
sort(words.begin(), words.end());
string ans = "";
sort(words.begin(), words.end(), [](const string& a, const string& b) {
return a.length() == b.length() ? a < b : a.length() < b.length();
});
for (const string& word : words) {
bool valid = true;
for (int i = 1; i < word.size(); ++i) {
if (wordSet.find(word.substr(0, i)) == wordSet.end()) {
valid = false;
break;
}
}
if (valid && word.length() > ans.length()) {
ans = word;
}
}
return ans;
}
};
import java.util.*;
class Solution {
public String longestWord(String[] words) {
Set<String> wordSet = new HashSet<>(Arrays.asList(words));
Arrays.sort(words);
Arrays.sort(words, (a, b) -> a.length() != b.length() ? a.length() - b.length() : a.compareTo(b));
String ans = "";
for (String word : words) {
boolean valid = true;
for (int i = 1; i < word.length(); ++i) {
if (!wordSet.contains(word.substring(0, i))) {
valid = false;
break;
}
}
if (valid && word.length() > ans.length()) {
ans = word;
}
}
return ans;
}
}
var longestWord = function(words) {
const wordSet = new Set(words);
words.sort();
words.sort((a, b) => a.length === b.length ? a.localeCompare(b) : a.length - b.length);
let ans = "";
for (let word of words) {
let valid = true;
for (let i = 1; i < word.length; ++i) {
if (!wordSet.has(word.substring(0, i))) {
valid = false;
break;
}
}
if (valid && word.length > ans.length) {
ans = word;
}
}
return ans;
};
Brute-force approach: For each word of length L, check all prefixes by scanning the list (O(N) per prefix). For all words, this is O(N * L * N), which is inefficient for large N.
Optimized approach:
This is efficient and suitable for the input constraints.
The key insight for this problem is to use a set for O(1) prefix lookups and to process words in a sorted order so that we always build up from shorter prefixes. This avoids unnecessary re-checks and ensures that we find the longest, lexicographically smallest word with all prefixes present. The final solution is both simple and efficient, leveraging basic data structures and sorting.