python|使用二进制搜索和滚动哈希查找最长的公共子串

352 阅读3分钟

给出两个字符串XY,任务是找出最长的共同子串的长度。

举例说明。

输入。X = "GeeksforGeeks", y = "GeeksQuiz" 输出。5
解释。最大的公共子串是 "Geeks",长度为5。

输入。X = "abcdxyz", y = "xyzabcd" 输出。4
解释。最大的公共子串是 "abcd",长度为4。

输入。X = "zxabcdezy", y = "yzabcdezx"
输出。6
解释。最长的公共子串是 "abcdez",长度为6。

使用动态编程解决最长共线的问题:

这个问题可以用动态编程在O(len(X) * len(Y))中解决,见此。在这篇文章中,我们将讨论一种有效的方法。

使用二进制搜索和滚动哈希的最长公共子串

先决条件:

观察一下。

如果两个字符串中都有一个长度为K的公共子串,那么就会有长度为0,1,...,K-1 的公共子串**。** 因此,可以应用答案的二进制搜索。

按照下面的步骤来实现这个想法。

  • 最小可能的答案(low)=0,最大可能的答案(high)=min(len(X), len(Y)),二进制搜索的范围将是**[0, min(len(X), len(Y))]**。
    • 对于每一个mid,检查是否存在 一个长度为mid的共同子串,如果存在则更新low*,* 否则更新high*。*
    • 为了检查是否存在长度为K的公共子串,可以使用多项式滚动哈希函数。
      • 遍历所有大小为K的窗口 字符串X 和字符串Y 并得到哈希值
      • 如果存在一个共同的哈希,则返回True,否则返回False。

下面是这个方法的实现。

Python3

# Python code to implement the approach
  
# Function to implement rolling hash
class ComputeHash:
  
    # Generates hash in O(n(log(n)))
    def __init__(self, s, p, mod):
        n = len(s)
        self.hash = [0] * n
        self.inv_mod = [0] * n
        self.mod = mod
        self.p = p
  
        p_pow = 1
        hash_value = 0
  
        for i in range(n):
            c = ord(s[i]) - 65 + 1
            hash_value = (hash_value + c * p_pow) % self.mod
            self.hash[i] = hash_value
            self.inv_mod[i] = pow(p_pow, self.mod - 2, self.mod)
            p_pow = (p_pow * self.p) % self.mod
  
    # Return hash of a window in O(1)
    def get_hash(self, l, r):
  
        if l == 0:
            return self.hash[r]
  
        window = (self.hash[r] - self.hash[l - 1]) % self.mod
        return (window * self.inv_mod[l]) % self.mod
  
# Function to get the longest common substring
def longestCommonSubstr(X, Y, n, m):
  
    p1, p2 = 31, 37
    m1, m2 = pow(10, 9) + 9, pow(10, 9) + 7
  
    # Initialize two hash objects
    # with different p1, p2, m1, m2
    # to reduce collision
    hash_X1 = ComputeHash(X, p1, m1)
    hash_X2 = ComputeHash(X, p2, m2)
  
    hash_Y1 = ComputeHash(Y, p1, m1)
    hash_Y2 = ComputeHash(Y, p2, m2)
  
    # Function that returns the existence
    # of a common substring of length k
    def exists(k):
  
        if k == 0:
            return True
  
        st = set()
          
        # Iterate on X and get hash tuple
        # of all windows of size k
        for i in range(n - k + 1):
            h1 = hash_X1.get_hash(i, i + k - 1)
            h2 = hash_X2.get_hash(i, i + k - 1)
  
            cur_window_hash = (h1, h2)
              
            # Put the hash tuple in the set
            st.add(cur_window_hash)
  
        # Iterate on Y and get hash tuple
        # of all windows of size k
        for i in range(m - k + 1):
            h1 = hash_Y1.get_hash(i, i + k - 1)
            h2 = hash_Y2.get_hash(i, i + k - 1)
  
            cur_window_hash = (h1, h2)
              
            # If hash exists in st return True
            if cur_window_hash in st:
                return True
        return False
  
    # Binary Search on length
    answer = 0
    low, high = 0, min(n, m)
  
    while low <= high:
        mid = (low + high) // 2
  
        if exists(mid):
            answer = mid
            low = mid + 1
        else:
            high = mid - 1
  
    return answer
  
  
# Driver Code
if __name__ == '__main__':
    X = 'GeeksforGeeks'
    Y = 'GeeksQuiz'
    print(longestCommonSubstr(X, Y, len(X), len(Y)))

输出

5
4
6

时间复杂度。O(n * log(m1)) + O(m * log((m1)) + O((n + m) * log(min(n, m))

  1. 生成哈希对象需要O(n*log(m1)),其中n是字符串的长度,m1 = pow(10, 9) + 7。
  2. 二进制搜索需要 O(log(min(n, m)),其中n, m是两个字符串的长度。
  3. 窗口的哈希值需要O(1)时间。
  4. 存在函数需要O(n+m)时间。

辅助空间。O(n + m)