算法复习——动态规划篇之最长公共子串问题
以下内容主要参考中国大学MOOC《算法设计与分析》,墙裂推荐希望入门算法的童鞋学习!
1. 问题背景
子串:给定序列中零个或多个连续的元素(如字符)组成的子序列
公共子串:
-
给定两个序列 X X X和 Y Y Y
-
公共子串示例
问题是如何求两个给定序列的最长公共子串?
2. 问题定义
最长公共子串问题(Longest Common Substring Problem)
输入:
- 序列 X = < x 1 , x 2 , … , x n > X=<x_1, x_2, \dots, x_n> X=<x1,x2,…,xn>和序列 Y = < y 1 , y 2 , … , y m > Y=<y_1, y_2, \dots, y_m> Y=<y1,y2,…,ym>
输出:
-
求解一个公共子串 Z = < z 1 , z 2 , … , z l > Z=<z_{1}, z_{2}, \dots, z_{l}> Z=<z1,z2,…,zl>,令 m a x ∣ Z ∣ max|Z| max∣Z∣,
s . t . Z = < x i , x i + 1 , … , x i + l − 1 > = < y j , y j + 1 , … , y j + l − 1 > s.t. Z=<x_i, x_{i+1}, \dots, x_{i+l-1}>=<y_j, y_{j+1}, \dots, y_{j+l-1}> s.t.Z=<xi,xi+1,…,xi+l−1>=<yj,yj+1,…,yj+l−1>
( 1 ≤ i ≤ n − l + 1 ; 1 ≤ j ≤ m − l + 1 ) (1 \leq i \leq n-l+1;1 \leq j \leq m-l+1) (1≤i≤n−l+1;1≤j≤m−l+1)
3. 动态规划
3.1 问题结构分析
-
给出问题表示:
- C [ i , j ] C[i,j] C[i,j]: X [ 1.. i ] X[1..i] X[1..i]和 Y [ 1.. j ] Y[1..j] Y[1..j]中,以 x i x_i xi和 y j y_j yj结尾的最长公共子串 Z [ 1.. l ] Z[1..l] Z[1..l]的长度
-
明确原始问题:
-
p
m
a
x
=
m
a
x
1
≤
i
≤
n
,
1
≤
j
≤
m
{
C
[
i
,
j
]
}
p_{max}=max_{1 \leq i \leq n, 1\leq j \leq m}\{C[i,j]\}
pmax=max1≤i≤n,1≤j≤m{C[i,j]}
- X [ 1.. n ] X[1..n] X[1..n]和 Y [ 1.. m ] Y[1..m] Y[1..m]中最长公共子串的长度
-
p
m
a
x
=
m
a
x
1
≤
i
≤
n
,
1
≤
j
≤
m
{
C
[
i
,
j
]
}
p_{max}=max_{1 \leq i \leq n, 1\leq j \leq m}\{C[i,j]\}
pmax=max1≤i≤n,1≤j≤m{C[i,j]}
3.2 递推关系建立
3.2.1 分析最优(子)结构
-
情况1: x i ≠ y j x_i \neq y_j xi=yj
C [ i , j ] = 0 C[i,j]=0 C[i,j]=0,因为不存在以 x i x_i xi和 y j y_j yj结尾的公共子串,无子问题
-
情况2: x i = y j x_i = y_j xi=yj
C [ i , j ] = C [ i − 1 , j − 1 ] + 1 C[i, j] = C[i-1, j-1] + 1 C[i,j]=C[i−1,j−1]+1
3.2.2 构造递推公式
C [ i , j ] = { 0 , x i ≠ y j C [ i − 1 , j − 1 ] + 1 , x i = y j C[i,j]=\left\{ \begin{array}{rcl} 0, & & {x_i \neq y_j}\\ C[i-1,j-1]+1, & & {x_i = y_j}\\ \end{array} \right. C[i,j]={0,C[i−1,j−1]+1,xi=yjxi=yj
3.3 自底向上计算
3.3.1 确定计算顺序
-
初始化: C [ i , 0 ] = C [ 0 , j ] = 0 C[i, 0]=C[0, j]=0 C[i,0]=C[0,j]=0(某序列长度为0时,最长公共子串为0)
-
递推公式:
C [ i , j ] = { 0 , x i ≠ y j C [ i − 1 , j − 1 ] + 1 , x i = y j C[i,j]=\left\{ \begin{array}{rcl} 0, & & {x_i \neq y_j}\\ C[i-1,j-1]+1, & & {x_i = y_j}\\ \end{array} \right. C[i,j]={0,C[i−1,j−1]+1,xi=yjxi=yj
3.3.2 依次求解问题
3.4 最优方案追踪
- 记录决策过程
- 最长公共子串末尾位置为 p m a x p_{max} pmax
- 最长公共子串长度为 l m a x l_{max} lmax
- 输出最优方案
- 最长公共子串 < x p m a x − l + 1 , x p m a x − l + 2 , … , x p m a x > <x_{p_{max}-l+1}, x_{p_{max}-l+2}, \dots, x_{p_{max}}> <xpmax−l+1,xpmax−l+2,…,xpmax>
4. 伪代码
Longest-Common-Substring(X, Y)
输入:两个字符串X,Y
输出:X和Y的最长公共子串
// 初始化
n ← length(X)
m ← length(Y)
新建二维数组C[0..n, 0..m]
l_max ← 0
p_max ← 0
for i ← 0 to n do
C[i, 0] ← 0
end
for j ← 0 to m do
C[0, j] ← 0
end
// 动态规划
for i ← 1 to n do
for j ← 1 to m do
if X[i] != Y[j] then
C[i, j] ← 0
end
else
C[i, j] ← C[i-1, j-1] + 1
if C[i, j] > l_max then
l_max ← C[i, j]
p_max ← i
end
end
end
end
Print-LCS(X, l_max, p_max)
输入:字符串X,l_max,p_max
输出:X和Y的最长公共子串
if l_max = 0 then
return NULL
end
if i ← (p_max - l_max + 1) to p_max do
print X[i]
end
该算法的时间复杂度是 O ( m n ) O(mn) O(mn)。