【问题标题】:Find the common ordered characters between two strings查找两个字符串之间的公共有序字符
【发布时间】:2020-10-21 18:25:50
【问题描述】:

给定两个字符串,找出两个字符串之间从左到右顺序相同的公共字符。

示例 1

string_1 = 'hcarry'
string_2 = 'sallyc'

Output - 'ay'

示例 2

string_1 = 'jenny'
string_2 = 'ydjeu'

Output - 'je'

示例 1 的说明 -

string_1string_2 之间的常见字符是 c、a、y。但是由于cstring_1 中位于ay 之前,在string_2 中位于ay 之后,因此我们不会在输出中考虑字符c。两个字符串之间的公共字符的顺序必须保持并且必须相同。

示例 2 的说明 -

string_1string_2 之间的常见字符是 j,e,y。但是由于ystring_2 中出现在je 之前,在string_1 中出现在je 之后,因此我们不会在输出中考虑字符y。两个字符串之间的公共字符的顺序必须保持并且必须相同。

我的方法 -

  1. 查找字符串之间的共同字符,然后将其存储在每个单独字符串的另一个变量中。

Example - 

string_1 = 'hcarry'
string_2 = 'sallyc'

Common_characters = c,a,y

string_1_com = cay
string_2_com = ayc

我在 Python 中使用sorted, counter, enumerate 函数来获取string_1_com and string_2_com

  1. 现在找到string_1_com and string_2_com 之间的最长公共子序列。您会得到输出结果。

这是蛮力解决方案。

对此的最佳解决方案是什么?

【问题讨论】:

  • 这些字符串的长度是否总是相同的?
  • @Gad...可能是的,长度相同

标签: python string algorithm data-structures


【解决方案1】:

这个算法在我的书中被称为字符串匹配。它在 O(mn) 中运行,其中 mn 是单词长度。我想它也可以在完整的单词上运行,最有效的将取决于预期的常见字母数量以及如何执行排序和过滤。我会为常见的字母字符串解释它,因为这样更容易。

这个想法是您查看 (m+1)*(n+1) 个节点的有向无环图。通过该图的每条路径(从左上角到右下角)都代表一种匹配单词的独特方式。我们要匹配字符串,并在单词中添加空格(-),以便它们与最多的常用字母对齐。例如cayayc 的结束状态是

cay-
-ayc

每个节点存储它所代表的部分匹配的最大匹配数,并且在算法结束时,末端节点将为我们提供最大匹配数。

我们从左上角开始,这里没有任何匹配,所以我们这里有 0 个匹配的字母(得分 0)。

    c a y
  0 . . .
a . . . .
y . . . .
c . . . .

我们将遍历此图,并使用来自先前节点的数据为每个节点计算匹配字母的最大数量。

节点连接左->右、上->下和对角-左上->右-下。

  • 向右移动表示消耗来自cay 的一个字母,并将我们到达的字母与插入ayc- 匹配。
  • 向下移动代表相反的情况(从ayc 消费并插入-cay)。
  • 沿对角线移动表示从每个单词中提取一个字母并匹配这些字母。

查看我们起始节点右侧的第一个节点,它表示匹配

c
-

并且这个节点(显然)只能从起始节点到达。

第一行和第一列中的所有节点都将为 0,因为它们都表示匹配一个或多个具有相同数量的 - 的字母。

我们得到图表

    c a y
  0 0 0 0
a 0 . . .
y 0 . . .
c 0 . . .

这就是设置,现在有趣的部分开始了。

查看第一个未计算的节点,它表示将子字符串 ca 匹配,我们想决定如何在匹配字母最多的情况下到达那里。

  • 备选方案 1:我们可以从左侧的节点到达那里。左边的节点代表匹配
-
a

所以通过选择这条路径来到达我们当前的节点

-c
a-

c- 匹配不会给我们正确的匹配,因此这条路径的分数是0(取自最后一个节点)加上0(刚刚匹配c/- 的分数)。所以这条路径的 0 + 0 = 0。

  • 方案2:我们可以从上面到这个节点,这个路径代表从上面移动
c   ->    c-
-         -a

这也给了我们0分。得分为 0。

  • 备选方案 3:我们可以从左上角到达该节点。这是从起始节点(根本没有)移动到从每个字母中消耗一个字符。那是匹配的
c
a

由于ca 是不同的字母,因此我们也得到此路径的 0 + 0 = 0。

    c a y
  0 0 0 0
a 0 0 . .
y 0 . . .
c 0 . . .

但是对于下一个节点,它看起来更好。我们仍然有这三种选择。 备选方案 1 和 2 总是给我们额外的 0 分,因为它们总是表示将字母与 - 匹配,所以这些路径会给我们得分 0。让我们继续备选方案 3。

对于我们当前的节点沿对角线移动意味着从

c   ->   ca
-        -a

这是一场比赛!

这意味着有一条通往该节点的路径给我们 1 分。我们扔掉 0 并保存 1。

    c a y
  0 0 0 0
a 0 0 1 .
y 0 . . .
c 0 . . .

对于这一行的最后一个节点,我们查看了三个备选方案,并意识到我们不会得到任何新点(新匹配),但我们可以使用之前的 1 点路径到达该节点:

ca   ->   cay
-a        -a-

所以这个节点的分数也是1。

对所有节点执行此操作,我们得到以下完整图

    c a y
  0 0 0 0
a 0 0 1 1
y 0 0 1 2
c 0 1 1 2

分数的唯一增加来自哪里

c   ->   ca   |   ca   ->   cay   |   -   ->   -c
-        -a   |   -a        -ay   |   y        yc

所以结束节点告诉我们最大匹配是 2 个字母。 由于在您的情况下您希望知道得分为 2 的最长路径,因此您还需要跟踪每个节点所采用的路径。

此图很容易实现为矩阵(或数组数组)。

我建议您作为元素使用tuple 和一个score 元素和一个path 元素,并且在路径元素中您只需存储对齐字母,那么最终矩阵的元素将是

    c      a        y
  0 0      0        0
a 0 0      (1, a)   (1, a)
y 0 0      (1, a)   (2, ay)
c 0 (1, c) (1, a/c) (2, ay)

在一个地方我注意到a/c,这是因为字符串caayc 有两个不同的最大长度子序列。您需要决定在这些情况下该怎么做,要么选择一个,要么两个都保存。

编辑:

这是此解决方案的实现。

def longest_common(string_1, string_2):
    len_1 = len(string_1)
    len_2 = len(string_2)
    
    m = [[(0,"") for _ in range(len_1 + 1)] for _ in range(len_2 + 1)] # intitate matrix
    
    for row in range(1, len_2+1):
        for col in range(1, len_1+1):
            diag = 0
            match = ""
            if string_1[col-1] == string_2[row-1]: # score increase with one if letters match in diagonal move
                diag = 1
                match = string_1[col - 1]
            # find best alternative
            if m[row][col-1][0] >= m[row-1][col][0] and m[row][col-1][0] >= m[row-1][col-1][0]+diag:
                m[row][col] = m[row][col-1] # path from left is best
            elif m[row-1][col][0] >= m[row-1][col-1][0]+diag:
                m[row][col] = m[row-1][col] # path from above is best
            else:
                m[row][col] = (m[row-1][col-1][0]+diag, m[row-1][col-1][1]+match) # path diagonally is best

    return m[len_2][len_1][1]
>>> print(longest_common("hcarry", "sallyc"))
ay
>>> print(longest_common("cay", "ayc"))
ay
>>> m
[[(0, ''), (0, ''), (0, ''), (0, '')],
 [(0, ''), (0, ''), (1, 'a'), (1, 'a')],
 [(0, ''), (0, ''), (1, 'a'), (2, 'ay')],
 [(0, ''), (1, 'c'), (1, 'c'), (2, 'ay')]]

【讨论】:

  • 这对我来说并不容易理解......不知何故,我迷失在解释本身中,无法理解你想说的......
  • @MeghaAggarwal 我想这会很安静。我会努力改进我的答案。
  • @MeghaAggarwal 还添加了一个实现
【解决方案2】:

这是一个简单的、基于动态规划的问题实现:

def lcs(X, Y): 
    m, n = len(X), len(Y)
    L = [[0 for x in xrange(n+1)] for x in xrange(m+1)] 
  
    # using a 2D Matrix for dynamic programming
    # L[i][j] stores length of longest common string for X[0:i] and Y[0:j]
    for i in range(m+1): 
        for j in range(n+1): 
            if i == 0 or j == 0: 
                L[i][j] = 0
            elif X[i-1] == Y[j-1]: 
                L[i][j] = L[i-1][j-1] + 1
            else: 
                L[i][j] = max(L[i-1][j], L[i][j-1]) 
  
    # Following code is used to find the common string 
    index = L[m][n] 
  
    # Create a character array to store the lcs string 
    lcs = [""] * (index+1) 
    lcs[index] = "" 
  
    # Start from the right-most-bottom-most corner and 
    # one by one store characters in lcs[] 
    i = m 
    j = n 
    while i > 0 and j > 0: 
  
        # If current character in X[] and Y are same, then 
        # current character is part of LCS 
        if X[i-1] == Y[j-1]: 
            lcs[index-1] = X[i-1] 
            i-=1
            j-=1
            index-=1
  
        # If not same, then find the larger of two and 
        # go in the direction of larger value 
        elif L[i-1][j] > L[i][j-1]: 
            i-=1
        else: 
            j-=1
  
    print ("".join(lcs))

【讨论】:

    【解决方案3】:

    但是..您已经知道“最长公共子序列”这个术语,并且可以找到大量动态规划算法的描述。
    Wiki link

    伪代码

    function LCSLength(X[1..m], Y[1..n])
        C = array(0..m, 0..n)
        for i := 0..m
            C[i,0] = 0
        for j := 0..n
            C[0,j] = 0
        for i := 1..m
            for j := 1..n
                if X[i] = Y[j] //i-1 and j-1 if reading X & Y from zero
                    C[i,j] := C[i-1,j-1] + 1
                else
                    C[i,j] := max(C[i,j-1], C[i-1,j])
        return C[m,n]
    
    function backtrack(C[0..m,0..n], X[1..m], Y[1..n], i, j)
        if i = 0 or j = 0
            return ""
        if  X[i] = Y[j]
            return backtrack(C, X, Y, i-1, j-1) + X[i]
        if C[i,j-1] > C[i-1,j]
            return backtrack(C, X, Y, i, j-1)
        return backtrack(C, X, Y, i-1, j)
    

    【讨论】:

    • 但这不是我认为的最佳解决方案,因为首先您需要按顺序找到常用字符串...
    • 动态编程按照您的需要按顺序查找常用字符。也许我不完全了解您的需求。
    【解决方案4】:

    更简单的解决方案-----谢谢!

    def f(s, s1):
     cc = list(set(s) & set(s1))
     ns = ''.join([S for S in s if S in cc])
     ns1 = ''.join([S for S in s1 if S in cc])
     found = []
     b = ns[0]
     for e in ns[1:]:
        cs = b+e
        if cs in ns1:
            found.append(cs)
        b = e
     return found
    

    【讨论】:

      猜你喜欢
      • 2013-09-13
      • 1970-01-01
      • 2013-04-18
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多