优化版链接 :【算法·动态规划+递归】 求两个字符串的按序排列的所有公共子串(优化版)
(基于本思路的算法优化版可参照上文。)
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
# 查找最大公共子串, 遍历所有点(省代码量)
for _row in range(1, len_a + 1):
for _col in range(1, len_b + 1):
if s_a[_row - 1] == s_b[_col - 1]:
substr_matrix[_row][_col] = substr_matrix[_row - 1][_col - 1] + 1
if substr_matrix[_row][_col] > current_max:
current_max = substr_matrix[_row][_col]
row_max, col_max = _row, _col
# 递归
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
问题:
求两个字符串的按序排列的所有公共子串,如"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。接着进行前向递归和后续递归。可以理解不断从矩阵中分割子矩阵,然后取出当前子矩阵中的最大值对应的字符子串。根据矩阵图可以发现,前向递归总是针对当前最大子串起始点的左上子矩阵进行递归运算,而后向递归总是针对当前最大子串末尾点的右下子矩阵进行递归运算。尽管在整个矩阵的左下方有一个连续的自增斜角数字段12,对应字符“qg”,但是它并不会被纳入到公共子串列表中;因为本算法是最大公共子串优先,所以即使“qg”是客观存在的公共子串,但由于它不符合最大公共子串优先和按序选择的规则,所以并不会被纳入。结果为['abcd', 'f']。