算法复习——动态规划篇之最长公共子序列问题

本文围绕最长公共子序列问题展开,先介绍问题背景与定义,通过枚举观察发现可能存在最优子结构和重叠子问题。接着运用动态规划,分析问题结构、建立递推关系、自底向上计算并追踪最优方案,最后给出伪代码,算法时间复杂度为O(nm)。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

算法复习——动态规划篇之最长公共子序列问题

以下内容主要参考中国大学MOOC《算法设计与分析》,墙裂推荐希望入门算法的童鞋学习!

1. 问题背景

子序列:将给定序列中零个或多个元素(如字符)去掉后所得结果

公共子序列

  • 给定两个序列 X X X Y Y Y

    在这里插入图片描述

  • 公共子序列示例

    在这里插入图片描述

​ 问题是如何求两个给定序列的最长公共子序列?

2. 问题定义

最长公共子序列问题(Longest Common Subsequence 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| maxZ s . t . < z 1 , z 2 , … , z l > = < x i 1 , x i 2 , … , x i l > = < y j 1 , y j 2 , … , y j l > s.t.<z_1,z_2,\dots,z_l>=<x_{i_1},x_{i_2},\dots,x_{i_l}>=<y_{j_1},y_{j_2},\dots,y_{j_l}> s.t.<z1,z2,,zl>=<xi1,xi2,,xil>=<yj1,yj2,,yjl>

    ( 1 ≤ i 1 < i 2 , … , i l ≤ n ; 1 ≤ j 1 < j 2 , … , j l ≤ m ) (1 \leq i_1 < i_2, \dots, i_l \leq n;1 \leq j_1 < j_2, \dots, j_l \leq m) (1i1<i2,,iln;1j1<j2,,jlm)

3. 枚举观察

在这里插入图片描述

​ 可能存在最优子结构重叠子问题

4. 动态规划

4.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]的最长公共子序列长度

    在这里插入图片描述

  • 明确原始问题

    • C [ n , m ] C[n,m] C[n,m] X [ 1.. n ] X[1..n] X[1..n] Y [ 1.. m ] Y[1..m] Y[1..m]的最长公共子序列长度

4.2 递推关系建立

4.2.1 分析最优(子)结构

考察末尾字符

  • x i ≠ y j x_i \neq y_j xi=yj

    在这里插入图片描述

    C [ i , j ] = m a x { C [ i − 1 , j ] , C [ i , j − 1 ] } C[i,j]=max\{C[i-1,j],C[i,j-1]\} C[i,j]=max{C[i1,j],C[i,j1]}

  • x i = y j x_i = y_j xi=yj

在这里插入图片描述

那是不是三个子问题都要去求解呢?实则不然,我们会发现:

  • C [ i − 1 , j ] C[i-1,j] C[i1,j] C [ i − 1 , j − 1 ] C[i-1,j-1] C[i1,j1]至多大1( C [ i − 1 , j ] ≤ C [ i − 1 , j − 1 ] + 1 C[i-1,j] \leq C[i-1,j-1]+1 C[i1,j]C[i1,j1]+1
  • C [ i , j − 1 ] C[i,j-1] C[i,j1] C [ i − 1 , j − 1 ] C[i-1,j-1] C[i1,j1]至多大1( C [ i , j − 1 ] ≤ C [ i − 1 , j − 1 ] + 1 C[i,j-1] \leq C[i-1,j-1]+1 C[i,j1]C[i1,j1]+1

所以在末尾字符相同时,只需要 C [ i , j ] = C [ i − 1 , j − 1 ] + 1 C[i,j]=C[i-1,j-1]+1 C[i,j]=C[i1,j1]+1这个递推式即可

4.2.2 构造递推公式

C [ i , j ] = { m a x { C [ i − 1 , j ] , C [ i , j − 1 ] } , x i ≠ y j C [ i − 1 , j − 1 ] + 1 , x i = y j C[i,j]=\left\{ \begin{array}{rcl} max\{C[i-1,j],C[i,j-1]\}, & & {x_i \neq y_j}\\ C[i-1,j-1]+1, & & {x_i = y_j}\\ \end{array} \right. C[i,j]={max{C[i1,j],C[i,j1]},C[i1,j1]+1,xi=yjxi=yj

4.3 自底向上计算

4.3.1 确定计算顺序
  • 初始化: C [ i , 0 ] = C [ 0 , i ] = 0 C[i,0]=C[0,i]=0 C[i,0]=C[0,i]=0(某序列长度为0时,最长公共子序列长度为0)

  • 递推公式:
    C [ i , j ] = { m a x { C [ i − 1 , j ] , C [ i , j − 1 ] } , x i ≠ y j C [ i − 1 , j − 1 ] + 1 , x i = y j C[i,j]=\left\{ \begin{array}{rcl} max\{C[i-1,j],C[i,j-1]\}, & & {x_i \neq y_j}\\ C[i-1,j-1]+1, & & {x_i = y_j}\\ \end{array} \right. C[i,j]={max{C[i1,j],C[i,j1]},C[i1,j1]+1,xi=yjxi=yj

在这里插入图片描述

4.3.2 依次求解问题

在这里插入图片描述

4.4 最优方案追踪

4.4.1 记录决策过程

​ 构造追踪数组 r e c [ 1.. n , 1.. m ] rec[1..n,1..m] rec[1..n,1..m],记录子问题来源。

  • 如果 r e c [ i , j ] = L U rec[i,j]=LU rec[i,j]=LU,最长公共子序列末尾为 X [ i ] = Y [ j ] X[i]=Y[j] X[i]=Y[j]
  • 如果 r e c [ i , j ] = U rec[i,j]=U rec[i,j]=U,最长公共子序列在 X [ 1.. i − 1 ] X[1..i-1] X[1..i1] Y [ 1.. j ] Y[1..j] Y[1..j]
  • 如果 r e c [ i , j ] = L rec[i,j]=L rec[i,j]=L,最长公共子序列在 X [ 1.. i ] X[1..i] X[1..i] Y [ 1.. j − 1 ] Y[1..j-1] Y[1..j1]

r e c [ i , j ] = { L U , i f   C [ i , j ] = C [ i − 1 , j − 1 ] + 1 U , i f   C [ i , j ] = C [ i − 1 , j ] L , i f   C [ i , j ] = C [ i , j − 1 ] rec[i,j]=\left\{ \begin{array}{rcl} LU, & & {if\ C[i,j]=C[i-1,j-1]+1}\\ U, & & {if\ C[i,j]=C[i-1,j]}\\ L, & & {if\ C[i,j]=C[i,j-1]} \end{array} \right. rec[i,j]=LU,U,L,if C[i,j]=C[i1,j1]+1if C[i,j]=C[i1,j]if C[i,j]=C[i,j1]

4.4.2 输出最优方案

在这里插入图片描述

5. 伪代码

Longest-Common-Subsequence(X, Y)

输入:两个序列X,Y

输出:X和Y的最长公共子序列

n ← length(X)
m ← length(Y)
// 初始化
新建二维数组C[0..n, 0..m]和rec[0..n, 0..m]
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] ← C[i-1, j-1] + 1
			rec[i, j] ← "LU"
		end
		else if C[i-1, j] >= C[i, j-1] then
			C[i, j] ← C[i-1, j]
			rec[i, j] ← "U"
		end
		else
			C[i, j] ← C[i, j-1]
			rec[i, j] ← "L"
		end
	end
end
return C, rec

Print-LCS(rec, X, i, j)

输入:追踪数组rec,序列X,当前位置i和j

输出:X[1…i]和Y[1…j]的最长公共子序列

if i = 0 or j = 0 then
	return NULL
end
if rec[i, j] = "LU" then
	Pring-LCS(rec, X, i-1, j-1)
	print X[i]
end
else if rec[i, j] = "U" then
	Print-LCS(rec, X, i-1, j)
end
else
	Print-LCS(rec, X, i, j-1)
end

​ 该算法的时间复杂度是 O ( n m ) O(nm) O(nm)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值