实验内容
实现以下内容:
- 编程实现最长公共子序列(LCS)算法,并理解其核心思想。
- 时间复杂度 O(mn),空间复杂度 O(mn),求出 LCS 及其长度。
- 时间复杂度 O(mn),空间复杂度 O(2*min(m,n)),求出 LCS 的长度。
- 时间复杂度 O(mn),空间复杂度 O(min(m,n)),求出 LCS 的长度。
最长公共子序列 LCS:
给定两个字符串 text1 和 text2,返回这两个字符串的最长公共子序列的长度。
一个字符串的 子序列 是指这样一个新的字符串:它是由原字符串在不改变字符的相对顺序的情况下删除某些字符(也可以不删除任何字符)后组成的新字符串。
例如,“ace” 是 “abcde” 的子序列,但 “aec” 不是 “abcde” 的子序列。两个字符串的「公共子序列」是这两个字符串所共同拥有的子序列。
若这两个字符串没有公共子序列,则返回 0。
程序输入:
由控制台输入两个字符串 text1,text2。
其中:
1 <= text1.length <= 1000
1 <= text2.length <= 1000
输入的字符串只含有小写英文字符(a~z)
程序输出:
控制台打印 LCS 的长度及相应的序列,如不存在公共子序列则返回 0.
示例 1:
输入:text1 = “abcde”, text2 = “ace”
输出: LCS:“ace”, 长度:3
示例 2:
输入: text1 = “abc”, text2 = “def”
输出:0
算法设计思路
requirement_1
- 创建一个二维数组dp,其中dp[i][j]表示字符串s的前i个字符和字符串t的前j个字符的最长公共子序列的长度。
- 遍历字符串s和t的每个字符,如果s[i-1]等于t[j-1],则说明当前字符可以加入最长公共子序列中,因此dp[i][j]等于dp[i-1][j-1] + 1;否则,当前字符不能加入最长公共子序列,需要选择之前的最长子序列,即dp[i-1][j]和dp[i][j-1]中的较大值。
- 从dp数组中倒推得到最长公共子序列。从dp[m][n]开始,如果s[i-1]等于t[j-1],则将该字符添加到最长公共子序列中,并将i和j分别减1;否则,根据dp[i-1][j]和dp[i][j-1]的大小关系,更新i或j的值。
- 输出最长公共子序列及其长度。
requirement_2
- 创建一个二维数组dp,其中dp[i % 2][j]表示字符串s的前i个字符和字符串t的前j个字符的最长公共子序列的长度。由于每次只用到dp[(i-1) % 2][j-1]、dp[i % 2][j-1]和dp[(i-1) % 2][j]这三个值,因此可以使用滚动数组进行空间优化。
- 遍历字符串s和t的每个字符,如果s[i-1]等于t[j-1],则说明当前字符可以加入最长公共子序列中,因此dp[i % 2][j]等于dp[(i-1) % 2][j-1] + 1;否则,当前字符不能加入最长公共子序列,需要选择之前的最长子序列,即dp[(i-1) % 2][j]和dp[i % 2][j-1]中的较大值。
- 输出dp[m % 2][n],即最长公共子序列的长度。
requirement_3
- 创建一个一维数组dp,其中dp[j]表示字符串s的前i个字符和字符串t的前j个字符的最长公共子序列的长度。在每次更新dp[j]之前,先将其值保存到变量prev中。
- 遍历字符串s和t的每个字符,如果s[i-1]等于t[j-1],则说明当前字符可以加入最长公共子序列中,因此dp[j]等于prev + 1;否则,当前字符不能加入最长公共子序列,需要选择之前的最长子序列,即dp[j]和dp[j-1]中的较大值。
- 在更新dp[j]之前,将dp[j]的值保存到prev中以备后续使用。
- 输出dp[n],即最长公共子序列的长度。
源码及注释
def lcs(s, t):
"""
计算最长公共子序列(LCS)的长度和序列本身
参数:
s:字符串类型,输入序列1
t:字符串类型,输入序列2
返回值:
无返回值,直接打印LCS序列和其长度
"""
m, n = len(s), len(t)
dp = [[0] * (n+1) for _ in range(m+1)]
for i in range(1, m+1):
for j in range(1, n+1):
if s[i-1] == t[j-1]:
dp[i][j] = dp[i-1][j-1] + 1
else:
dp[i][j] = max(dp[i-1][j], dp[i][j-1])
i, j = m, n
lcs = ""
while i > 0 and j > 0:
if s[i-1] == t[j-1]:
lcs = str(s[i-1]) + lcs
i -= 1
j -= 1
elif dp[i-1][j] >= dp[i][j-1]:
i -= 1
else:
j -= 1
print(lcs)
print(len(lcs))
def lcs_length(s, t):
"""
计算最长公共子序列(LCS)的长度
参数:
s:字符串类型,输入序列1
t:字符串类型,输入序列2
返回值:
打印LCS序列的长度
"""
m, n = len(s), len(t)
if m < n:
s, t = t, s
m, n = n, m
dp = [[0] * (n+1) for _ in range(2)]
for i in range(1, m+1):
for j in range(1, n+1):
if s[i-1] == t[j-1]:
dp[i % 2][j] = dp[(i-1) % 2][j-1] + 1
else:
dp[i % 2][j] = max(dp[(i-1) % 2][j], dp[i % 2][j-1])
print(dp[m % 2][n])
def lcs_length_1(s, t):
"""
计算最长公共子序列(LCS)的长度
参数:
s:字符串类型,输入序列1
t:字符串类型,输入序列2
返回值:
打印LCS序列的长度
"""
m, n = len(s), len(t)
if m < n:
s, t = t, s
m, n = n, m
dp = [0] * (n+1)
for i in range(1, m+1):
prev = 0
for j in range(1, n+1):
temp = dp[j]
if s[i-1] == t[j-1]:
dp[j] = prev + 1
else:
dp[j] = max(dp[j], dp[j-1])
prev = temp
print(dp[n])
def main_1():
"""
主函数,读取输入数据,调用各个函数计算最长公共子序列
"""
with open('data.txt', 'r', encoding='utf-8') as file:
lines = file.readlines()
s = lines[0]
t = lines[1]
file.close()
print("requirement_1:")
lcs(s, t)
print("requirement_2:")
lcs_length(s, t)
print("requirement_3:")
lcs_length_1(s, t)
def main_2():
"""
主函数,手动输入序列,调用各个函数计算最长公共子序列
"""
s = 'asdasdfghjkl'
t = 'fghasdfghjkl'
print("requirement_1:")
lcs(s, t)
print("requirement_2:")
lcs_length(s, t)
print("requirement_3:")
lcs_length_1(s, t)
if __name__ == "__main__":
main_1()