【问题标题】:Longest matching substring irrespective of the order of characters最长匹配子串,与字符顺序无关
【发布时间】:2013-06-04 15:55:58
【问题描述】:

我找不到以下假设面试问题的答案:

给定两个长度为 N 的字符串序列,无论顺序如何,如何找到匹配子串的最大长度。

例如,给定seq1 = "ABCDEFG"seq2 = "DBCAPFG",最大长度窗口为4。(ABCD 来自seq1DBCA 来自seq2)。

【问题讨论】:

  • 变异是指字母可以重新排列吗?
  • @jamylak 是的,你是对的。类似于字谜的东西,另一个字符串中的序列可以是第一个字符串中序列的字谜,反之亦然。
  • 这叫置换
  • 我可以想到一个简单的 N^2 解决方案,但我很想知道它是否可以更快地完成。
  • ABCD 和 BCDE 的最大窗口是多少?

标签: string algorithm substring


【解决方案1】:

这肯定不是最佳算法,但还没有人提供功能齐全的解决方案,所以这里有一个简单的方法:

def sort_possible_substrings(p_str):
    """The function iterates through every possible substring of p_str and 
    sorts the characters within the substring and finally inserts them in 
    a set which will be returned."""

    S = set()
    # the possible length of the substrings of p_str
    for s_len in range(1, len(p_str)+1):
        # the substrings of p_str with s_len length
        for s in range(len(p_str)-(s_len-1)):
            sorted_substr = ''.join(sorted(p_str[s:s+s_len]))
            S.add(sorted_substr)
    return S


def longest_common_perm_len(str1, str2):
    """Build up a set from the substrings of the shorter string with the help of 
    sort_possible_substrings. Iterate through the possible substrings of the
    longer string from the longest to the shortest and check whether the same
    string is contained by "candidates" thus by the shorter string."""
    shorter, longer = (str1,str2) if len(str1) <= len(str2) else (str2,str1)
    candidates = sort_possible_substrings(shorter)
    for win_len in range(len(shorter),0,-1):
        for s in range(len(longer)-(win_len-1)):
            str_chunk = ''.join(sorted(longer[s:s+win_len]))
            if str_chunk in candidates:
                return win_len
    return 0

(子字符串中字符的排序可以用 Peter de Rivaz 的“直方图”解决方案代替。)

这个不是最优的、直接的算法给出:

lsubstr.longest_common_perm_len("ABCDZZEFG", "XDBCAXFEG") -> 4
lsubstr.longest_common_perm_len("ABCD", "XDXACXB") -> 1

【讨论】:

    【解决方案2】:

    假设

    对于给定的数组 A[0..n], B[0..m]

    • 有限字母表
    • 不重复的字符

       ans = -1
       j = index of A[0] in B
       if j > n then no substring exists
       for 1 .. n
           find A[i] in B[0..j]
           if not found
               j = find A[i] in B[j+1..m]
               if not found, return last ans
           else 
               if i == j then ans = j
      

    如果 B[0..j] 被排序(也许构造二叉树 P),那么在 B[0..j] 中搜索 A[i] 将是 O(log n) 并且整个算法将是 O (n log n)

    帮助验证这一点会很棒。
    有反例吗?

    【讨论】:

      【解决方案3】:

      这是一个 O(n) 的解决方案(假设固定字母大小和 O(1) 字典访问)。

      使用单个频率表,对 seq1 中的字符进行向上计数,对 seq2 中的字符进行向下计数。如果此直方图再次采用相同的值,我们将有一个匹配窗口(因为这意味着我们必须有相同数量的中间字符)。

      所以我们使用字典来存储直方图以前的值。

      Python 实现:

      def find_window(seq1,seq2):
          """Yield length of matching windows"""
          D={} # Dictionary with key=histogram, value = first position where this happens
          H=[0]*26 # H[x] is count of x in seq1 - count of x in seq2
          D[tuple(H)]=-1
          for i,(a,b) in enumerate(zip(seq1,seq2)):
              a=ord(a)-ord('A')
              b=ord(b)-ord('A') 
              H[a]+=1
              H[b]-=1
              key=tuple(H)
              if key in D:
                  yield i-D[key]
              if key not in D:
                  D[key]=i
      
      print max(find_window("ABCDEFG","DBCAPFG"))
      

      如果您有更大的字母表,您可以使用类似的技术,只存储哈希值而不是完整的直方图。例如,您可以简单地为每个字符选择一个随机代码,然后添加 seq 中每个字母的代码,或者减去 seq2 中的字母。

      如果发生哈希冲突,您将需要第二遍检查建议的匹配是否正确。

      【讨论】:

      • 我不懂python,通常无法通过代码理解算法。最好用纯文本解释你的算法。然后我们可以看看是否正确(分别为upvote或downvote)。
      • 彼得,此代码似乎仅在窗口开始于与字符串开头相同的距离时才有效,即它们都从第 0 个或第 3 个或第 i 个索引开始。如果你用两个字符串尝试这个算法 - “XABCDEFGYZ”,“YZDBACPFGX”,这个算法将给出 0 个最大窗口。请注意,在这种情况下,窗口分别从第二和第三索引开始。如果我们再运行一​​个外部循环,通过在每次迭代中将下一个字符串的索引增加 1 来运行此算法,那么它将给出正确的结果。
      • @texens 您是否看到 cmets 中对主要问题的澄清? Amit 说 ABCD 和 BCDE 之间的最大窗口是 0,所以我认为在你的情况下,正确答案确实是零。
      • @PeterdeRivaz 你误会了,在“ABCD”和“BCDE”的情况下,窗口为零,因为缺少“A”,在“XABCDEFGYZ”,“YZDBACPFGX”中,max_window 应该是 4 (str1 中 1-4,str2 中 2-5)
      • @texens 抱歉,我毕竟不明白这个问题。也许 Amit 可以发布一些简单的蛮力代码来说明正确的操作?
      猜你喜欢
      • 1970-01-01
      • 2019-07-19
      • 2022-06-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2016-01-23
      相关资源
      最近更新 更多