找到对列表进行排序的替换

时间:2016-11-24 18:32:14

标签: algorithm encryption computer-science letter

请考虑以下词语:

PINEAPPLE
BANANA
ARTICHOKE
TOMATO

目标是对它进行排序(按字典顺序排列),而不是移动单词本身,而是使用字母替换。在这个例子中,我可以用A代替字母P,用P代替A,所以:

AINEPAALE
BPNPNP
PRTICHOKE
TOMPTO

这是按字典顺序排列的列表。如果你切换字母,字母将被切换为所有单词。值得注意的是,您可以使用整个字母表,仅使用列表中单词中的字母。

我花了相当多的时间来解决这个问题,但是除了暴力强迫它(尝试所有的字母开关组合)之外我还没有想到任何其他东西,也没有能够在列表可以时提出定义的条件分类

更多例子:

ABC
ABB
ABD

可以变成

ACB
ACC
ACD

满足条件。

3 个答案:

答案 0 :(得分:4)

让我们假设特定情况下的问题是可能的,就在现在。此外,为简单起见,假设所有单词都是不同的(如果两个单词相同,则它们必须相邻,一个可以忽略)。

然后问题转变为拓扑排序,尽管细节与可疑狗的答案略有不同,后者错过了几个案例。

考虑26个节点的图表,标记为AZ。每对单词对部分排序贡献一个有向边;这对应于单词不同的第一个字符。例如,按顺序使用两个单词ABCEFABRKS,第一个区别在于第三个字符,因此sigma(C) < sigma(R)

可以通过对此图进行拓扑排序,并将A替换为排序中的第一个节点,B替换第二个节点等来获得结果。

请注意,这也可以衡量问题何时无法解决。当两个单词相同但不相邻时(在#34;簇&#34;中),当一个单词是另一个单词的前缀但是在它之后,或者当图形具有循环并且拓扑排序是不可能的时候,则会发生这种情况。

这是Python中一个功能齐全的解决方案,可以检测问题的特定实例何时无法解析。

def topoSort(N, adj):
    stack = []
    visited = [False for _ in range(N)]
    current = [False for _ in range(N)]

    def dfs(v):
        if current[v]: return False # there's a cycle!
        if visited[v]: return True
        visited[v] = current[v] = True
        for x in adj[v]:
            if not dfs(x):
                return False
        current[v] = False
        stack.append(v)
        return True

    for i in range(N):
        if not visited[i]:
            if not dfs(i):
                return None

    return list(reversed(stack))

def solve(wordlist):
    N = 26
    adj = [set([]) for _ in range(N)] # adjacency list
    for w1, w2 in zip(wordlist[:-1], wordlist[1:]):
        idx = 0
        while idx < len(w1) and idx < len(w2):
            if w1[idx] != w2[idx]: break
            idx += 1
        else:
            # no differences found between the words
            if len(w1) > len(w2):
                return None
            continue

        c1, c2 = w1[idx], w2[idx]
        # we want c1 < c2 after the substitution
        adj[ord(c1) - ord('A')].add(ord(c2) - ord('A'))

    li = topoSort(N, adj)
    sub = {}
    for i in range(N):
        sub[chr(ord('A') + li[i])] = chr(ord('A') + i)
    return sub

def main():
    words = ['PINEAPPLE', 'BANANA', 'ARTICHOKE', 'TOMATO']
    print('Before: ' + ' '.join(words))
    sub = solve(words)
    nwords = [''.join(sub[c] for c in w) for w in words]
    print('After : ' + ' '.join(nwords))

if __name__ == '__main__':
    main()

编辑:此解决方案的时间复杂度是可证明最佳的O(S),其中S是输入的长度。感谢suspicious dog为此;原始时间复杂度为O(N^2 L)

答案 1 :(得分:1)

更新:正如Eric Zhang所指出的那样,原始分析是错误的并且在某些测试用例类上失败了。

我相信这可以通过topological sort的形式解决。您的初始单词列表定义了某组字母的部分顺序或有向图。您希望找到使该字母图线性化的替换。让我们使用您的一个非平凡的例子:

P A R K O V I S T E
P A R A D O N T O Z A
P A D A K
A B B A
A B E C E D A
A B S I N T

x <* y表示某些字母(或单词)substitution(x) < substitution(y)x y。我们总体上需要word1 <* word2 <* word3 <* word4 <* word5 <* word6,但就字母而言,我们只需要查看每对相邻的单词,并在同一列中找到第一对不同的字符:

K <* A  (from PAR[K]OVISTE <* PAR[A]DONTOZA)
R <* D  (from PA[R]ADONTOZA <* PA[D]AK)
P <* A  (from [P]ADAK <* [A]BBA)
B <* E  (from AB[B]A <* AB[E]CEDA)
E <* S  (from AB[E]CEDA <* AB[S]INT)

如果我们发现没有不匹配的字母,则有3种情况:

  1. 字1和字2是相同的
  2. 字1是字2的前缀
  3. 字2是字1的前缀
  4. 在案例1和案例2中,单词已经按字典顺序排列,因此我们不需要执行任何替换(尽管我们可能),并且它们不会添加我们需要遵守的额外约束。在案例3中,没有任何替代可以解决这个问题(想想["DOGGO", "DOG"]),因此没有可能的解决方案,我们可以提前退出。

    否则,我们构建与我们获得的部分排序信息相对应的有向图并执行拓扑排序。如果排序过程指示不可能进行线性化,则没有用于对单词列表进行排序的解决方案。否则,你会得到类似的东西:

    P <* K <* R <* B <* E <* A <* D <* S
    

    根据您实现拓扑排序的方式,您可能会获得不同的线性排序。现在,您只需要为每个字母分配一个符合此排序的替换,并按字母顺序排序。一个简单的选择是将线性排序与自己按字母顺序排序,并将其用作替换:

    P <* K <* R <* B <* E <* A <* D <* S
    |    |    |    |    |    |    |    |
    A <  B <  D <  E <  K <  P <  R <  S
    

    但如果您愿意,可以实施不同的替换规则。

    这是Python中的概念验证:

    import collections
    import itertools
    
    # a pair of outgoing and incoming edges
    Edges = collections.namedtuple('Edges', 'outgoing incoming')
    # a mapping from nodes to edges
    Graph = lambda: collections.defaultdict(lambda: Edges(set(), set()))
    
    def substitution_sort(words):
        graph = build_graph(words)
    
        if graph is None:
            return None
    
        ordering = toposort(graph)
    
        if ordering is None:
            return None
    
        # create a substitition that respects `ordering`
        substitutions = dict(zip(ordering, sorted(ordering)))
    
        # apply substititions
        return [
            ''.join(substitutions.get(char, char) for char in word)
            for word in words
        ]
    
    def build_graph(words):
        graph = Graph()
    
        # loop over every pair of adjacent words and find the first
        # pair of corresponding characters where they differ
        for word1, word2 in zip(words, words[1:]):
            for char1, char2 in zip(word1, word2):
                if char1 != char2:
                    break
            else: # no differing characters found...
    
                if len(word1) > len(word2):
                    # ...but word2 is a prefix of word1 and comes after;
                    # therefore, no solution is possible
                    return None
                else:
                    # ...so no new information to add to the graph
                    continue
    
            # add edge from char1 -> char2 to the graph
            graph[char1].outgoing.add(char2)
            graph[char2].incoming.add(char1)
    
        return graph
    
    def toposort(graph):
        "Kahn's algorithm; returns None if graph contains a cycle"
        result = []
        working_set = {node for node, edges in graph.items() if not edges.incoming}
    
        while working_set:
            node = working_set.pop()
            result.append(node)
            outgoing = graph[node].outgoing
    
            while outgoing:
                neighbour = outgoing.pop()
                neighbour_incoming = graph[neighbour].incoming
                neighbour_incoming.remove(node)
    
                if not neighbour_incoming:
                    working_set.add(neighbour)
    
        if any(edges.incoming or edges.outgoing for edges in graph.values()):
            return None
        else:
            return result
    
    def print_all(items):
        for item in items:
            print(item)
        print()
    
    def test():    
        test_cases = [
            ('PINEAPPLE BANANA ARTICHOKE TOMATO', True),
            ('ABC ABB ABD', True),
            ('AB AA AB', False),
            ('PARKOVISTE PARADONTOZA PADAK ABBA ABECEDA ABSINT', True),
            ('AA AB CA', True),
            ('DOG DOGGO DOG DIG BAT BAD', False),
            ('DOG DOG DOGGO DIG BIG BAD', True),
        ]
    
        for words, is_sortable in test_cases:
            words = words.split()
            print_all(words)
    
            subbed = substitution_sort(words)
    
            if subbed is not None:
                assert subbed == sorted(subbed), subbed
                print_all(subbed)
            else:
                print('<no solution>')
                print()
    
            print('expected solution?', 'yes' if is_sortable else 'no')
            print()
    
    if __name__ == '__main__':
        test()
    

    现在,它并不理想 - 例如,即使原始的单词列表已经排序,它仍会执行替换 - 但它似乎有效。我无法正式证明它有效,所以如果你找到反例,请告诉我!

答案 2 :(得分:0)

  1. 提取列表中每个单词的所有首字母。 (P,B,A,T)
  2. 对列表进行排序。 (A,B,P,T)
  3. 将所有出现的单词中的第一个字母替换为已排序列表中的第一个字符。
  4. 用所有单词替换P( P ineapple)。

    用B代替所有单词中的B.

    用P。

    替换所有单词中的A.

    用T替换所有单词中的T.

    这将为您提供预期的结果。

    编辑:

    1. 比较两个相邻的字符串。如果一个更大而不是另一个,那么找到第一次出现的字符不匹配并交换并用交换的字符替换所有单词。
    2. 对整个列表重复此操作,例如冒泡排序。
    3. 示例 -

      ABC&lt; ABB

      第一次出现字符不匹配的是第3位。所以我们将所有的C与B&C进行交换。