Given a list of strings words
representing an English Dictionary, find the longest word in words
that can be built one character at a time by other words in words
. If there is more than one possible answer, return the longest word with the smallest lexicographical order.
If there is no answer, return the empty string.
Example 1:
Input: words = ["w","wo","wor","worl", "world"] Output: "world" Explanation: The word "world" can be built one character at a time by "w", "wo", "wor", and "worl".
Example 2:
Input: words = ["a", "banana", "app", "appl", "ap", "apply", "apple"] Output: "apple" Explanation: Both "apply" and "apple" can be built from other words in the dictionary. However, "apple" is lexicographically smaller than "apply".
Note:
- All the strings in the input will only contain lowercase letters.
- The length of
words
will be in the range[1, 1000]
. - The length of
words[i]
will be in the range[1, 30]
.
這道題給了我們一個字典,是個字符串數組,然后問我們從單個字符開始拼,最長能組成啥單詞,注意中間生成的字符串也要在字典中存在,而且當組成的單詞長度相等時,返回字母順序小的那個。好,看到這么多前綴一樣多字符串,是不是很容易想到用前綴樹來做,其實我們並不需要真正的建立前綴樹結點,可以借鑒查找的思想來做。那么為了快速的查找某個單詞是否在字典中存在,我們將所有單詞放到哈希集合中,在查找的時候,可以采用BFS或者DFS都行。先來看BFS的做法,使用一個queue來輔助,我們先把所有長度為1的單詞找出排入queue中,當作種子選手,然后我們進行循環,每次從隊首取一個元素出來,如果其長度大於我們維護的最大值mxLen,則更新mxLen和結果res,如果正好相等,也要更新結果res,取字母順序小的那個。然后我們試着增加長度,做法就是遍歷26個字母,將每個字母都加到單詞后面,然后看是否在字典中存在,存在的話,就加入queue中等待下一次遍歷,完了以后記得要恢復狀態,參見代碼如下:
解法一:
class Solution { public: string longestWord(vector<string>& words) { string res = ""; int mxLen = 0; unordered_set<string> s(words.begin(), words.end()); queue<string> q; for (string word : words) { if (word.size() == 1) q.push(word); } while (!q.empty()) { string t = q.front(); q.pop(); if (t.size() > mxLen) { mxLen = t.size(); res = t; } else if (t.size() == mxLen) { res = min(res, t); } for (char c = 'a'; c <= 'z'; ++c) { t.push_back(c); if (s.count(t)) q.push(t); t.pop_back(); } } return res; } };
下面來看遞歸的解法,前面都一樣,不同在於直接對長度為1的單詞調用遞歸函數,在遞歸中,還是先判斷單詞和mxLen關系來更新結果res,然后就是遍歷所有字符,加到單詞后面,如果在集合中存在,調用遞歸函數,結束后恢復狀態,參見代碼如下:
解法二:
class Solution { public: string longestWord(vector<string>& words) { string res = ""; int mxLen = 0; unordered_set<string> s(words.begin(), words.end());for (string word : words) { if (word.size() == 1) helper(s, word, mxLen, res); } return res; } void helper(unordered_set<string>& s, string word, int& mxLen, string& res) { if (word.size() > mxLen) { mxLen = word.size(); res = word; } else if (word.size() == mxLen) { res = min(res, word); } for (char c = 'a'; c <= 'z'; ++c) { word.push_back(c); if (s.count(word)) helper(s, word, mxLen, res); word.pop_back(); } } };
下面這種解法是論壇上的高分解法,其實我們只要給數組排個序,就可以使用貪婪算法來做了,並不需要什么DFS或BFS這么復雜。首先建立一個空的哈希set,然后我們直接遍歷排序后的字典,對於當前的單詞,如果當前單詞長度為1,或者該單詞去掉最后一個字母后在集合中存在,這也不難理解,長度為1,說明是起始單詞,不需要的多余的判斷,否則的話就要判斷其去掉最后一個字母后的單詞是否在集合中存在,存在的話,才說明其中間單詞都存在,因為此時是從短單詞向長單詞遍歷,只要符合要求的才會加入集合,所以一旦其去掉尾字母的單詞存在的話,那么其之前所有的中間情況都會在集合中存在。我們更新結果res時,要判斷當前單詞長度是否大於結果res的長度,因為排序過后,默認先更新的字母順序小的單詞,所有只有當當前單詞長度大,才更新結果res,之后別忘了把當前單詞加入集合中,參見代碼如下:
解法三:
class Solution { public: string longestWord(vector<string>& words) { string res = ""; unordered_set<string> s; sort(words.begin(), words.end()); for (string word : words) { if (word.size() == 1 || s.count(word.substr(0, word.size() - 1))) { res = (word.size() > res.size()) ? word : res; s.insert(word); } } return res; } };
類似題目:
Longest Word in Dictionary through Deleting
參考資料:
https://discuss.leetcode.com/topic/109643/java-c-clean-code