求最长公共子串可以先求最长公共子串的长度,并且记录那些公共子串字符的长度以及字符,然后通过回溯可以找到所有的公共子串。
下面是求最长公共子串长度的动态规划方法。
1:决策,我们在最后一步需要做的决策是,是否要将A[n],B[m]加入公共子串序列中。
2:由 1 可知,若以DP[i][j]表示A[1..i] 与 B[1..j]的最长公共子串的长度,那么可以得到
(1) 若A[i] == B[j] (即作出决策,将A[i],B[i]都加入公共子串)
DP[i][j] = DP[i - 1][j - 1] + 1
(2) 若A[i] != B[j] (即将A[i] B[i] 最多一个加入公共子串,具体值由DP[i-1][j] 和DP[i][j-1]确定
DP[i][j] = max(DP[i-1][j],DP[i][j-1]
至此,得到动态规划的第一个特点,最优子结构,即每次对问题的求解(对DP[i][j]的求解),都可以转换为对最优子结构的处理(即DP[i-1][j],DP[i][j-1],DP[i-1][j-1],这三种情况所包含的子串都是最优子结构,所以对 DP[i][j] 的求解就可以变成对上面三种情况所包含的 "当前最长子串" 的长度求解)
3: 求每一个DP[i][j],由于每次求DP[i][j],都需要重复求解DP[i-1][j-1],所以可以用一个矩阵记录每个DP[i][j]的值,该矩阵就是 备忘录
这里得到动态规划的第二特点,子问题重复求解,不同于 诸如二叉树遍历的 递归算法,每次都在遍历之前从未遍历的节点, 也不同于 贪心算法 ,每次都对不变化的子情况 调用贪心策略
4: 对备忘录进行优化,以及加入回溯信息
由于我们的问题是求最长子串,所以我们并不关心遍历完A,B串之前的那些 "当前最长子串"
所以可以在求得D[i][1..n]之后,用D[i][1..n]覆盖掉之前的D[i-1][1..n]
使得备忘录由 矩阵 变为 一维数组 ,空间复杂度由O( m * n),变成 O( min(m,n) )
加入回溯信息:
最常规的方法是每次求最大长度的时候,把整个已经得到的前缀都保存起来,但是那样会浪费大量的存储空间,还可能使得对 备忘录 的优化变得毫无意义
可以发现
我们在求子串的最大长度的过程中,是知道在什么时候(即A[i]==B[j] 时,并且此时的子串长度为DP[i][j]),在子串中加入了哪个字符