如何寻找具有给定前缀和后缀的最长的词

201 阅读8分钟

问题陈述

给你一个单词列表,一个前缀和一个后缀。你必须在给定的单词列表中找到具有给定前缀和后缀的最长的单词。例如,如果给定的前缀和后缀分别是PS ,那么你必须找到模式为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结尾,这在这里成立。但是它的长度比之前最长的词要小。所以,我们不会把它设为最长的。即最长的=苹果

接下来,我们检查这个是否分别以ae开始和结束。它并不成立。所以,我们移到下一个词,仍然是最长的=苹果

接下来,我们检查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数据结构来解决问题的想法非常简单。我们可以分别创建两个triesprefixTriesuffixTrie。现在我们可以将所有的词按正向插入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。
prefixtrie

这是给定词表的后缀trie。
suffixtrie

当我们查询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)。

编码愉快
!🤗🤗🤗再见!👋👋👋见