📌  相关文章
📜  查找字母顺序,以便单词可以被认为是排序的

📅  最后修改于: 2022-05-13 01:57:07.308000             🧑  作者: Mango

查找字母顺序,以便单词可以被认为是排序的

给定一个单词数组,在英文字母表中找到任何字母顺序,使得给定的单词可以被认为是排序(递增),如果存在这样的顺序,否则输出不可能。

例子:

Input :  words[] = {"zy", "ab"}
Output : zabcdefghijklmnopqrstuvwxy
Basically we need to make sure that 'z' comes
before 'a'.

Input :  words[] = {"geeks", "gamers", "coders", 
                    "everyoneelse"}
Output : zyxwvutsrqponmlkjihgceafdb

Input : words[] = {"marvel", "superman", "spiderman", 
                                           "batman"
Output : zyxwvuptrqonmsbdlkjihgfeca

天真的方法:蛮力方法是检查所有可能的顺序,并检查它们中的任何一个是否满足给定的单词顺序。考虑到英语中有26个字母,有26 个!可以是有效订单的排列数。考虑到我们检查每一对以验证订单,这种方法的复杂度达到O(26!*N^2) ,这远远超出了实际首选的时间复杂度。

使用拓扑排序:此解决方案需要了解图形及其作为邻接列表、DFS 和拓扑排序的表示。

在我们要求的顺序中,需要打印字母,使得每个字母后面必须跟着比它们优先级低的字母。它似乎有点类似于拓扑排序的定义——在拓扑排序中,我们需要在其相邻顶点之前打印一个顶点。让我们将字母表中的每个字母定义为标准有向图中的节点。如果A在顺序上先于B ,则称A连接到B (A—>B)。该算法可以表述如下:

  1. 如果n1 ,那么任何订单都是有效的。
  2. 取前两个词。识别单词中的第一个不同字母(在单词的相同索引处)。第一个单词中的字母将在第二个单词中的字母之前。
  3. 如果不存在这样的字母,则第一个字符串的长度必须小于第二个字符串。
  4. 将第二个单词分配给第一个单词,并将第三个单词输入到第二个单词中。重复234 (n-1)次。
  5. 按拓扑顺序运行 DFS 遍历。
  6. 检查是否访问了所有节点。在拓扑顺序中,如果图中存在环,则环中的节点保持未访问过,因为在访问了与它相邻的每个节点之后,不可能访问这些节点。在这种情况下,订单不存在。在这种情况下,这意味着我们列表中的顺序自相矛盾。
/* CPP program to find an order of alphabets
so that given set of words are considered
sorted */
#include 
using namespace std;
#define MAX_CHAR 26
  
void findOrder(vector v)
{
    int n = v.size();
  
    /* If n is 1, then any order works */
    if (n == 1) {
        cout << "abcdefghijklmnopqrstuvwxyz";
        return;
    }
  
    /* Adjacency list of 26 characters*/
    vector adj[MAX_CHAR];
  
    /* Array tracking the number of edges that are 
    inward to each node*/
    vector in(MAX_CHAR, 0);
  
    // Traverse through all words in given array
    string prev = v[0];
  
    /* (n-1) loops because we already acquired the 
    first word in the list*/
    for (int i = 1; i < n; ++i) {
        string s = v[i];
  
        /* Find first such letter in the present string that is different 
        from the letter in the previous string at the same index*/
        int j;
        for (j = 0; j < min(prev.length(), s.length()); ++j)
            if (s[j] != prev[j])
                break;
  
        if (j < min(prev.length(), s.length())) {
  
            /* The letter in the previous string precedes the one
            in the present string, hence add the letter in the present
            string as the child of the letter in the previous string*/
            adj[prev[j] - 'a'].push_back(s[j] - 'a');
  
            /* The number of inward pointing edges to the node representing 
            the letter in the present string increases by one*/
            in[s[j] - 'a']++;
  
            /* Assign present string to previous string for the next 
            iteration. */
            prev = s;
            continue;
        }
  
        /* If there exists no such letter then the string length of 
        the previous string must be less than or equal to the 
        present string, otherwise no such order exists*/
        if (prev.length() > s.length()) {
            cout << "Impossible";
            return;
        }
  
        /* Assign present string to previous string for the next
        iteration */
        prev = s;
    }
  
    /* Topological ordering requires the source nodes 
    that have no parent nodes*/
    stack stk;
    for (int i = 0; i < MAX_CHAR; ++i)
        if (in[i] == 0)
            stk.push(i);
  
    /* Vector storing required order (anyone that satisfies) */
    vector out;
  
    /* Array to keep track of visited nodes */
    bool vis[26];
    memset(vis, false, sizeof(vis));
  
    /* Standard DFS */
    while (!stk.empty()) {
  
        /* Acquire present character */
        char x = stk.top();
        stk.pop();
  
        /* Mark as visited */
        vis[x] = true;
  
        /* Insert character to output vector */
        out.push_back(x + 'a');
  
        for (int i = 0; i < adj[x].size(); ++i) {
            if (vis[adj[x][i]])
                continue;
  
            /* Since we have already included the present 
            character in the order, the number edges inward 
            to this child node can be reduced*/
            in[adj[x][i]]--;
  
            /* If the number of inward edges have been removed, 
            we can include this node as a source node*/
            if (in[adj[x][i]] == 0)
                stk.push(adj[x][i]);
        }
    }
  
    /* Check if all nodes(alphabets) have been visited.
    Order impossible if any one is unvisited*/
    for (int i = 0; i < MAX_CHAR; ++i)
        if (!vis[i]) {
            cout << "Impossible";
            return;
        }
  
    for (int i = 0; i < out.size(); ++i)
        cout << out[i];
}
  
// Driver code
int main()
{
    vector v{ "efgh", "abcd" };
    findOrder(v);
    return 0;
}

输出 :

zyxwvutsrqponmlkjihgfeadcb

这种方法的复杂性是O(N*|S|) + O(V+E) ,其中|V| =26(节点数与字母数相同)和|E| < N (因为每个单词最多创建 1 个边作为输入)。因此总体复杂度为O(N*|S|+N)|S|表示每个单词的长度。