问题陈述
给你一个单词列表,一个前缀和一个后缀。你必须在给定的单词列表中找到具有给定前缀和后缀的最长的单词。例如,如果给定的前缀和后缀分别是P 和S ,那么你必须找到模式为P*S 的最长的单词。
如果有多个词满足给定的条件,则返回词典上最小的一个词。如果没有词满足条件,则返回一个空字符串。
比如说:
wordlist = ["apple", "appe", "word", "alloye"]
prefix = "a"
suffix = "e"
Output: alloye
如合金e是以所给前缀和后缀开始和结束的最长的词。
方法1:不使用三段论
这个问题的明显解决方案是循环浏览单词列表中的每个单词。检查该词是否以给定的前缀和后缀开始和结束。如果是,则检查其长度是否大于前缀。最后返回最长的词。
算法
步骤1:创建一个变量longest,并将其初始化为空字符串
步骤2:对于单词列表中的每个单词从头到尾进行循环:
步骤3:如果该单词以给定的前缀和后缀开始和结束:
步骤4:如果该单词的长度大于longest的长度:
步骤5:将longest设为单词
步骤6:如果该单词的长度等于longest的长度:
步骤7:如果该单词在词典中小于longest:
步骤8:将longest设为单词
步骤9 返回单词
举例说明
假设给定的单词表是["apple", "appe", "word", " alloye"],我们必须找到前缀为a、后缀为e的最长的单词。
让我们创建一个变量longest并将其初始化为一个空字符串,即longest = ""。
现在,检查一下苹果这个词是否以a开头,以e结尾,这对苹果来说是成立的。因此,将longest设为苹果,即longest = apple。
接下来,检查appe这个词是否以a开头,以e结尾,这在这里成立。但是它的长度比之前最长的词要小。所以,我们不会把它设为最长的。即最长的=苹果。
接下来,我们检查这个词是否分别以a和e开始和结束。它并不成立。所以,我们移到下一个词,仍然是最长的=苹果。
接下来,我们检查alloye这个词是否以a开头,以e 结尾。它也成立,因为它的长度大于当前最长的词。因此,我们将其设置为最长的,即最长的= alloye。
由于单词表中没有剩余的单词,我们返回当前最长的单词,即我们返回alloye。
Java实现
class LongestPrefixAndSuffix {
public static String longestWord(String[] words, String prefix, String suffix) {
String longest = "";
for(String s: words) {
if(s.startsWith(prefix) && s.endsWith(suffix)) {
if(s.length() > longest.length()) {
longest = s;
}
if(s.length() == longest.length()) {
longest = (s.compareTo(longest) < 0)? s: longest;
}
}
}
return longest;
}
public static void main(String[] args) {
String[] words = {"apple", "ape", "word", "alloe"};
String word = longestWord(words, "a", "e");
System.out.println(word);
}
}
C++实现
#include <iostream>
#include <vector>
using namespace std;
bool starts_with(string str, string prefix)
{
if(str.length() < prefix.length())
return false;
for(int i = 0;i < prefix.length();i++)
{
if(str[i] != prefix[i])
return false;
}
return true;
}
bool ends_with(string word, string suffix)
{
int lw = word.length();
int ls = suffix.length();
if(lw < ls)
return false;
int j = ls - 1;
for(int i = lw - 1;j >= 0;i--,j--)
{
if(word[i] != suffix[j])
return false;
}
return true;
}
string longestWord(vector<string>& s, string prefix, string suffix)
{
string longest = "";
for(auto word: s)
{
if(starts_with(word, prefix) and ends_with(word, suffix))
{
if(word.length() > longest.length()) {
longest = word;
}
if(word.length() == longest.length() and word < longest) {
longest = word;
}
}
}
return longest;
}
int main()
{
vector<string> s = {"apple", "ape", "word", "aqoye"};
string str = longestWord(s, "s", "e");
cout << str << endl;
}
注意:在C++20中,我们不需要实现start_with()和ends_with()函数,因为它已经包含在字符串类中。
时间复杂度
该算法的时间复杂度是函数longestWord()的时间复杂度。假设函数longestWord()花费的时间是T(lw),函数start_with()花费的时间是T(sw),函数ends_with()花费的时间是T(ew)。
同样,比较字符串需要O(n)
时间,n是字符串的长度。那么:
T(lw) = N * (T(sw) + T(ew))
我们可以看到,T(sw) = O(p),p是前缀的长度,
T(ew) = O(s),s是后缀的长度
所以,总的时间复杂性将是O(N * M)
,其中N是词表的长度,M
是词表中单词的平均长度。
空间复杂度
由于我们没有使用任何额外的空间,空间复杂性将是O(1)。
方法2:使用Trie
对于字典搜索来说,三段式数据结构被认为是最好的数据结构。因此,我们可以采用Trie数据结构来解决问题。
使用Trie数据结构来解决问题的想法非常简单。我们可以分别创建两个triesprefixTrie和suffixTrie。现在我们可以将所有的词按正向插入prefixTrie,这将使我们能够搜索任何带有给定前缀的词的存在,并按反向插入suppixTrie,这将使我们能够搜索任何带有给定后缀的词的存在。
操作步骤
步骤1:创建两个尝试,即prefixTrie和suffixTrie
步骤2 :将所有单词从单词的前面到后面插入prefixTrie 步骤
步骤3:将所有单词从单词的后面到前面插入suffixTrie
步骤4:查询prefixTrie,返回所有以给定前缀开始的字符串的索引,并将其存储在list1中
步骤5:查询suffixTrie,返回所有以给定后缀结尾的字符串的索引,并将它们存储在list2中
步骤6:在list1和list2中循环,得到所有分别以给定前缀和后缀开头和结尾的单词的索引:
步骤7:比较以给定前缀开头和后缀结尾的单词的长度,找出最长的一个
步骤8 :返回最长的单词
注::上述步骤要求学生事先了解Trie数据结构以及其中的插入和搜索操作。
举例说明
我们假设wordlist = ["apple", "appe", "word", " alloye"]。
这里是给定词表的前缀trie。
这是给定词表的后缀trie。
当我们查询prefixTrie中所有以a开头的单词的索引时,它返回[0, 1, 3]。而当查询以supix开头的单词列表时,supixTrie返回[0, 1, 3]。
现在,我们在两个返回的列表中找到共同的索引,我们得到[0, 1, 3]。
创建一个变量longest,并将其初始化为一个空字符串。
接下来,我们将longest与索引为0的单词apple进行比较,并将longest设置为apple。
再次,将longest与索引为1的单词appe进行比较,longest仍然是apple。
现在,我们将longest与索引为3的单词alloye进行比较,发现它比apple要长。所以,把它设为最长的,即最长的= alloye
由于已经没有索引可供比较,我们返回当前最长的,即alloye。
Java实现
import java.util.*;
class Solution {
public static String longestWord(String[] words, String prefix, String suffix) {
Trie prefixTrie = new Trie();
Trie suffixTrie = new Trie();
//inserts all the words into the prefixTrie from the front of the word
prefixTrie.insertAll(words);
//inserts all the words into the suffixTrie from the back of the word
suffixTrie.insertAllReverse(words);
//the indexes of all the words that start with the given prefix in sorted order
ArrayList<Integer> prefixAt = prefixTrie.searchPrefix(prefix);
//the indexes of all the words that ends with given suffix in sorted order
ArrayList<Integer> suffixAt = suffixTrie.searchSuffix(suffix);
//holds the indexes of all the words that start with and end with the given prefix and suffix respectively
ArrayList<Integer> wordsAt = new ArrayList<>();
int n = prefixAt.size();
int m = suffixAt.size();
int i = 0, j = 0;
while(i < n && j < m) {
if(prefixAt.get(i) == suffixAt.get(j)) {
wordsAt.add(prefixAt.get(i));
i++;
j++;
}
else if(prefixAt.get(i) > suffixAt.get(j))
j++;
else
i++;
}
String longest = "";
for(i = 0;i < wordsAt.size();i++) {
if(words[wordsAt.get(i)].length() > longest.length())
longest = words[wordsAt.get(i)];
if(words[wordsAt.get(i)].length() == longest.length() && words[wordsAt.get(i)].compareTo(longest) < 0)
longest = words[wordsAt.get(i)];
}
return longest;
}
public static void main(String[] args) {
String[] words = {"apple", "ape", "word", "aqoye"};
String longest = longestWord(words, "a", "e");
System.out.println(longest);
}
}
class Trie {
class Node {
Node[] links;
ArrayList<Integer> indeces;
Node() {
links = new Node[26];
indeces = new ArrayList<Integer>();
}
//checks if the character is present
boolean containsKey(char ch) {
return links[ch - 'a'] != null;
}
//returns the node referenced by the character
Node get(char ch) {
return links[ch - 'a'];
}
//adds a node to links array
void put(char ch, Node node) {
links[ch - 'a'] = node;
}
//adds the index of the word in indeces
void addIndex(int index) {
indeces.add(index);
}
//returns all index of all the words that has given prefix or suffix
ArrayList<Integer> getList() {
return indeces;
}
}
Node root;
Trie() {
root = new Node();
}
void insert(String word, int index) {
Node node = root;
for(char ch: word.toCharArray()) {
if(!node.containsKey(ch))
node.put(ch, new Node());
node = node.get(ch);
node.addIndex(index);
}
}
//adds the reverse of the word into the trie
void insertReverse(String word, int index) {
Node node = root;
for(int i = word.length() - 1;i >= 0;i--) {
if(!node.containsKey(word.charAt(i)))
node.put(word.charAt(i), new Node());
node = node.get(word.charAt(i));
node.addIndex(index);
}
}
void insertAll(String[] words) {
for(int i = 0;i < words.length;i++) {
insert(words[i], i);
}
}
void insertAllReverse(String[] words) {
for(int i = 0;i < words.length; i++) {
insertReverse(words[i], i);
}
}
ArrayList<Integer> searchPrefix(String str) {
Node node = root;
for(char ch: str.toCharArray()) {
if(!node.containsKey(ch))
return new ArrayList<Integer>();
node = node.get(ch);
}
return node.getList();
}
ArrayList<Integer> searchSuffix(String str) {
Node node = root;
int len = str.length();
for(int i = len - 1;i >= 0;i--) {
if(!node.containsKey(str.charAt(i)))
return new ArrayList<Integer>();
node = node.get(str.charAt(i));
}
return node.getList();
}
}
C++实现
#include <iostream>
#include <vector>
using namespace std;
struct Node {
Node* links[26];
vector<int> indexes;
bool containsKey(char ch) {
return links[ch - 'a'] != nullptr;
}
void put(char ch) {
links[ch - 'a'] = new Node();
}
Node* get(char ch) {
return links[ch - 'a'];
}
void addIndex(int index) {
indexes.push_back(index);
}
vector<int> getIndexes() {
return indexes;
}
};
class Trie {
private:
Node* root;
public:
Trie() {
root = new Node();
}
void insert(string word, int index) {
Node* node = root;
for(char ch: word) {
if(!node->containsKey(ch))
node->put(ch);
node = node->get(ch);
node->addIndex(index);
}
}
void insertReverse(string word, int index) {
Node* node = root;
for(int i = word.size() - 1;i >= 0;i--) {
if(!node->containsKey(word[i]))
node->put(word[i]);
node = node->get(word[i]);
node->addIndex(index);
}
}
void insertAll(vector<string> words) {
int i = 0;
for(string word: words) {
insert(word, i++);
}
}
void insertAllReverse(vector<string> words) {
for(int i = 0;i < words.size();i++) {
insertReverse(words[i], i);
}
}
vector<int> searchPrefix(string str) {
Node* node = root;
for(char ch: str) {
if(!node->containsKey(ch))
return vector<int>(0);
node = node->get(ch);
}
return node->getIndexes();
}
vector<int> searchSuffix(string word) {
Node* node = root;
for(int i = word.size() - 1;i >= 0;i--) {
if(!node->containsKey(word[i]))
return vector<int>(0);
node = node->get(word[i]);
}
return node->getIndexes();
}
};
string longestWord(vector<string> words, string prefix, string suffix) {
Trie* prefixTrie = new Trie();
Trie* suffixTrie = new Trie();
//inserts all the words into trie from its front to back
prefixTrie->insertAll(words);
//inserts all the words into trie from its back to front i.e. in reverse order
suffixTrie->insertAllReverse(words);
//indexes of all the words that starts with given prefix
vector<int> list1 = prefixTrie->searchPrefix(prefix);
//indexes of all the words that ends with given suffix
vector<int> list2 = suffixTrie->searchSuffix(suffix);
//indexes of all the words that start and end with the given prefix and suffix respectively
vector<int> list;
int n = list1.size();
int m = list2.size();
int i = 0, j = 0;
while(i < n and j < m) {
if(list1[i] == list2[j]) {
list.push_back(list1[i]);
i++;
j++;
}
else if(list1[i] > list2[j]) {
j++;
}
else {
i++;
}
}
string longest = "";
i = 0;
for(i = 0;i < list.size();i++) {
if(words[list[i]].size() > longest.size())
longest = words[list[i]];
if(words[list[i]].size() == longest.size() && words[list[i]] < longest)
longest = words[list[i]];
}
return longest;
}
int main() {
vector<string> s = {"apple", "ape", "word", "alloye"};
string str = longestWord(s, "ap", "e");
cout << str << endl; //output: apple
}
时间复杂度
将所有的词插入trie需要ΣO**(ni**),ni是词表中第1个字符串的长度。搜索前缀和后缀分别需要O(p)和O(s)的时间,其中p和s分别是前缀和后缀的长度。在最坏的情况下,寻找分别以给定的前缀和后缀开始和结束的词需要O(n)时间,其中n是词表的长度。
将所有这些加在一起,我们就得到了寻找满足条件的最长的词的时间复杂性。因此,函数longestWord()的时间复杂度将是ΣO(ni+ p + s + n)。
如果我们认为m是词表中单词的平均长度,那么时间复杂度为O(n * m)
空间复杂度
由于我们正在创建两个尝试,并将所有的字符插入一个 trie 中,空间复杂度可以近似为 ΣO(ni)。
编码愉快
!🤗🤗🤗再见!👋👋👋见