简洁版链接:【算法·动态规划+递归】 求两个字符串的按序排列的所有公共子串
本思路的代码简洁版可参考上文,基本思路一致但效率低于本文介绍的算法。本文介绍的方法与上文的介绍的唯一区别在于矩阵赋值的遍历路径:上文采用横向遍历+竖向遍历,遍历所有的矩阵节点;本算法采用斜角赋值,当剩余未匹配的矩阵点可能组成的最长字符子串的长度必然小于当前已获取的公共子串的长度时退出流程,过程中不会进行无效赋值。例如提取“gabcdfqg”和“qgabcdf”时,上文遍历了len(“gabcdfqg”) * len(“qgabcdf”) = 56个节点,而本算法遍历了2*(7 + 6)=26个节点便得出了当前的最长字符子串(遍历到斜角数列123456时,已知当前最长的字符子串长度为6,由于未遍历的斜角线的长度上限均小于6,所以其余斜角线均不遍历)。
问题:
求两个字符串的按序排列的所有公共子串,如"abcdufqg"、"qgabcdf"的公共子串列表为['abcd', 'f']。
思路:
- 生成(len("abcdufqg") + 1) * (len("qgabcdf") + 1)的空矩阵,矩阵行row = len("abcdufqg") + 1,列col = len("qgabcdf") + 1。各矩阵点的值默认为0。其中第0行和第0列的作用是辅助行/列,用于减少后续计算中判断某一行/列是否为首行/列的流程。
- 已知该矩阵的第N行/第M列(M、N初始值为0)的数据都对应"abcdufqg"的第N个字符/"qgabcdf"的第M个字符。斜向遍历矩阵点,如果遍历到的矩阵点的行、列对应的字符相同,则将该矩阵点的值加1,并加上左上斜角的数。当出现连续相同的字符子串时,矩阵中会出现连续自增的斜角数字段。
3.实时更新并记录当前遍历过程中所遍历到的最大值,如"abcdufqg"、"qgabcdf"生成的矩阵的最大值为4。接着进行前向递归和后续递归。可以理解不断从矩阵中分割子矩阵,然后取出当前子矩阵中的最大值对应的字符子串。根据矩阵图可以发现,前向递归总是针对当前最大子串起始点的左上子矩阵进行递归运算,而后向递归总是针对当前最大子串末尾点的右下子矩阵进行递归运算。结果为['abcd', 'f']。
def extract_common_substr_list_length_first_by_recursion(s_a, s_b):
"""
最大长度优先,按序获取两个字符串的所有公共子串(不去重)
eg. extract_common_substr_list_length_first_by_recursion("ababcababcdefq", "qabcdabcef") = ["abcd", "ef"]
eg. extract_common_substr_list_length_first_by_recursion("abcdefabcpq", "abcggabced") = ['abc', 'abc']
"""
result = []
len_a, len_b = len(s_a), len(s_b)
if len_a == 0 or len_b == 0 or (len_a == 1 and len_b == 1 and s_a != s_b):
return result
# init matrix(array) row:(len_a + 1) col:(len_b + 1)
substr_matrix = [[0] * (len_b + 1) for _ in range(len_a + 1)]
# 初始化最大公共子串的长度 && 最大公共子串末尾字符在矩阵中的坐标
current_max, row_max, col_max = -1, 0, 0
# 查找最大公共子串, 斜向匹配相对于遍历所有点而言效率更高
length_limit = min(len_a, len_b)
row_beg, col_beg = 1, 2
while length_limit > (current_max + 1):
while row_beg <= (len_a + 1 - length_limit):
for _tmp_offset in range(0, length_limit):
if s_a[row_beg + _tmp_offset - 1] == s_b[_tmp_offset]:
substr_matrix[row_beg + _tmp_offset][_tmp_offset + 1] = substr_matrix[row_beg + _tmp_offset - 1][_tmp_offset] + 1
if substr_matrix[row_beg + _tmp_offset][_tmp_offset + 1] > current_max:
current_max = substr_matrix[row_beg + _tmp_offset][_tmp_offset + 1]
row_max, col_max = row_beg + _tmp_offset, _tmp_offset + 1
row_beg += 1
while 1 < col_beg <= len_b:
for _tmp_offset in range(0, min(length_limit, len_b - col_beg + 1)):
if s_a[_tmp_offset] == s_b[col_beg + _tmp_offset - 1]:
substr_matrix[_tmp_offset + 1][col_beg + _tmp_offset] = substr_matrix[_tmp_offset][col_beg + _tmp_offset - 1] + 1
if substr_matrix[_tmp_offset + 1][col_beg + _tmp_offset] > current_max:
current_max = substr_matrix[_tmp_offset + 1][col_beg + _tmp_offset]
row_max, col_max = _tmp_offset + 1, col_beg + _tmp_offset
col_beg += 1
length_limit -= 1
# 递归
if current_max > 0: # 若存在公共子串
result += extract_common_substr_list_length_first_by_recursion(s_a[:row_max - current_max], s_b[:col_max - current_max]) # 前向递归
result.append(s_a[row_max - current_max: row_max])
result += extract_common_substr_list_length_first_by_recursion(s_a[row_max:], s_b[col_max:]) # 后向递归
return result