时间限制:1秒
空间限制:32768K
给定一个字符串s,你可以从中删除一些字符,使得剩下的串是一个回文串。如何删除才能使得回文串最长呢?
输出需要删除的字符个数。
输入描述:
输入数据有多组,每组包含一个字符串s,且保证:1<=s.length<=1000.
输出描述:
对于每组数据,输出一个整数,代表最少需要删除的字符个数。
输入例子1:
abcda
google
输出例子1:
2 2
提到回文串,自然要利用回文串的特点,想到将源字符串逆转后,“回文串”(不一定连续)相当于顺序没变
求原字符串和其反串的最大公共子序列(不是子串,因为可以不连续)的长度(使用动态规划很容易求得),然后用原字符串的长度减去这个最大公共子串的长度就得到了最小编辑长度。
import sys
def lcsLength(x,y):
m = len(x) + 1
n = len(y) + 1
# 存放数值
c = [[0 for i in range(n)] for j in range(m)]
for i in range(1, m):
for j in range(1, n):
# 数组第一个 元素 下标为 0
if x[i-1] == y[j-1]:
c[i][j] = c[i-1][j-1] + 1;
elif c[i-1][j] >= c[i][j-1]:
c[i][j] = c[i-1][j]
else:
c[i][j] = c[i][j-1]
return c[-1][-1]
if __name__ == '__main__':
while (True):
mystr= sys.stdin.readline().strip()
if len(mystr)==0:
break
re_mystr=mystr[::-1]
sameCount=lcsLength(mystr,re_mystr)
delCount=len(mystr)-sameCount
print(delCount)
为了讲解计算LCS(A,B),特给予以下几个定义
A=a1a2……aN,表示A是由a1a2……aN这N个字符组成,Len(A)=N
B=b1b2……bM,表示B是由b1b2……bM这M个字符组成,Len(B)=M
定义LCS(i,j)=LCS(a1a2……ai,b1b2……bj),其中0≤i≤N,0≤j≤M
对于1≤i≤N,1≤j≤M,有公式一
若ai=bj,则LCS(i,j)=LCS(i-1,j-1)+1
若ai≠bj,则LCS(i,j)=Max(LCS(i-1,j-1),LCS(i-1,j),LCS(i,j-1))
举例说明:A=GGATCGA,B=GAATTCAGTTA,计算LCS(A,B)
第一步:初始化LCS矩阵
Needleman/Wunsch算法矩阵
| | G | A | A | T | T | C | A | G | T | T | A |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
G | 0 | | | | | | | | | | | |
G | 0 | | | | | | | | | | | |
A | 0 | | | | | | | | | | | |
T | 0 | | | | | | | | | | | |
C | 0 | | | | | | | | | | | |
G | 0 | | | | | | | | | | | |
A | 0 | | | | | | | | | | | |
第二步:利用公式一,计算矩阵的第一行
Needleman/Wunsch算法矩阵
| | G | A | A | T | T | C | A | G | T | T | A |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
G | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
G | 0 | | | | | | | | | | | |
A | 0 | | | | | | | | | | | |
T | 0 | | | | | | | | | | | |
C | 0 | | | | | | | | | | | |
G | 0 | | | | | | | | | | | |
A | 0 | | | | | | | | | | | |
第三步:利用公式一,计算矩阵的其余各行
Needleman/Wunsch算法矩阵
| | G | A | A | T | T | C | A | G | T | T | A |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
G | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
G | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 2 | 2 | 2 |
A | 0 | 1 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 |
T | 0 | 1 | 2 | 2 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
C | 0 | 1 | 2 | 2 | 3 | 3 | 4 | 4 | 4 | 4 | 4 | 4 |
G | 0 | 1 | 2 | 2 | 3 | 3 | 3 | 4 | 5 | 5 | 5 | 5 |
A | 0 | 1 | 2 | 3 | 3 | 3 | 3 | 4 | 5 | 5 | 5 | 6 |
则,LCS(A,B)=LCS(7,11)=6
可以看出,Needleman/Wunsch算法实际上和LD算法是非常接近的。故他们的时间复杂度和空间复杂度也一样。时间复杂度为O(MN),空间复杂度为O(MN)。空间复杂度经过优化,可以优化到O(M),但是一旦优化就丧失了计算匹配字串的机会了。由于代码和LD算法几乎一样。这里就不再贴代码了。
还是以上面为例A=GGATCGA,B=GAATTCAGTTA,LCS(A,B)=6
他们的匹配为:
A:GGA_TC_G__A
B:GAATTCAGTTA
如上面所示,蓝色表示完全匹配,黑色表示编辑操作,_表示插入字符或者是删除字符操作。如上面所示,蓝色字符有6个,表示最长公共子串长度为6。
利用上面的Needleman/Wunsch算法矩阵,通过回溯,能找到匹配字串
第一步:定位在矩阵的右下角
Needleman/Wunsch算法矩阵
| | G | A | A | T | T | C | A | G | T | T | A |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
G | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
G | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 2 | 2 | 2 |
A | 0 | 1 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 |
T | 0 | 1 | 2 | 2 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
C | 0 | 1 | 2 | 2 | 3 | 3 | 4 | 4 | 4 | 4 | 4 | 4 |
G | 0 | 1 | 2 | 2 | 3 | 3 | 3 | 4 | 5 | 5 | 5 | 5 |
A | 0 | 1 | 2 | 3 | 3 | 3 | 3 | 4 | 5 | 5 | 5 | 6 |
第二步:回溯单元格,至矩阵的左上角
若ai=bj,则回溯到左上角单元格
Needleman/Wunsch算法矩阵
| | G | A | A | T | T | C | A | G | T | T | A |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
G | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
G | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 2 | 2 | 2 |
A | 0 | 1 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 |
T | 0 | 1 | 2 | 2 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
C | 0 | 1 | 2 | 2 | 3 | 3 | 4 | 4 | 4 | 4 | 4 | 4 |
G | 0 | 1 | 2 | 2 | 3 | 3 | 3 | 4 | 5 | 5 | 5 | 5 |
A | 0 | 1 | 2 | 3 | 3 | 3 | 3 | 4 | 5 | 5 | 5 | 6 |
若ai≠bj,回溯到左上角、上边、左边中值最大的单元格,若有相同最大值的单元格,优先级按照左上角、上边、左边的顺序
Needleman/Wunsch算法矩阵
| | G | A | A | T | T | C | A | G | T | T | A |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
G | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
G | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 2 | 2 | 2 |
A | 0 | 1 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 |
T | 0 | 1 | 2 | 2 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
C | 0 | 1 | 2 | 2 | 3 | 3 | 4 | 4 | 4 | 4 | 4 | 4 |
G | 0 | 1 | 2 | 2 | 3 | 3 | 3 | 4 | 5 | 5 | 5 | 5 |
A | 0 | 1 | 2 | 3 | 3 | 3 | 3 | 4 | 5 | 5 | 5 | 6 |
若当前单元格是在矩阵的第一行,则回溯至左边的单元格
若当前单元格是在矩阵的第一列,则回溯至上边的单元格
Needleman/Wunsch算法矩阵
| | G | A | A | T | T | C | A | G | T | T | A |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
G | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
G | 0 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 2 | 2 | 2 | 2 |
A | 0 | 1 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 | 2 |
T | 0 | 1 | 2 | 2 | 3 | 3 | 3 | 3 | 3 | 3 | 3 | 3 |
C | 0 | 1 | 2 | 2 | 3 | 3 | 4 | 4 | 4 | 4 | 4 | 4 |
G | 0 | 1 | 2 | 2 | 3 | 3 | 3 | 4 | 5 | 5 | 5 | 5 |
A | 0 | 1 | 2 | 3 | 3 | 3 | 3 | 4 | 5 | 5 | 5 | 6 |
若当前单元格是在矩阵的第一行,则回溯至左边的单元格
若当前单元格是在矩阵的第一列,则回溯至上边的单元格
依照上面的回溯法则,回溯到矩阵的左上角
第三步:根据回溯路径,写出匹配字串
搜索晚整个匹配路径,匹配字串也就完成了
代码实现:
# coding=utf-8
# 计算LCS长度算法
# 算法导论课本 动态规划 LCS 代码示例
# LCS 根据伪码 使用 python实现
def lcsLength(x,y):
m = len(x) + 1
n = len(y) + 1
# 存放箭头方向
b = [[0 for i in range(n)] for j in range(m)]
# 存放数值
c = [[0 for i in range(n)] for j in range(m)]
# 已经全部初始化为 0 了 上↑ 左← 左上↖
for i in range(1, m):
for j in range(1, n):
# 数组第一个 元素 下标为 0
if x[i-1] == y[j-1]:
c[i][j] = c[i-1][j-1] + 1;
b[i][j] = '↖'
elif c[i-1][j] >= c[i][j-1]:
c[i][j] = c[i-1][j]
b[i][j] = '↑'
else:
c[i][j] = c[i][j-1]
b[i][j] = '←'
return c, b
# 打印 x 和 y 的一个LCS
def printLcs(b, x, i, j):
if i == 0 or j == 0:
return
if b[i][j] == '↖':
printLcs(b, x, i - 1, j-1)
print(x[i-1],end='')#i的值要比x的索引大一,只打印斜向上箭头所对应的值,打印结果顺序输出
elif b[i][j] == '↑':
printLcs(b, x, i-1, j)
else:
printLcs(b, x, i, j-1)
x = ['A', 'B', 'C', 'B', 'D', 'A', 'B']
y = ['B', 'D', 'C', 'A', 'B', 'A']
c, b = lcsLength(x, y)
print("其最长公共子序列长度为:", c[len(x)][len(y)])
print("其最长公共子序列为:")
printLcs(b, x, len(x), len(y))
['A', 'B', 'C', 'B', 'D', 'A', 'B']
['B', 'D', 'C', 'A', 'B', 'A']
[0, 0, 0, 0, 0, 0, 0]
[0, 0, 0, 0, 1, 1, 1]
[0, 1, 1, 1, 1, 2, 2]
[0, 1, 1, 2, 2, 2, 2]
[0, 1, 1, 2, 2, 3, 3]
[0, 1, 2, 2, 2, 3, 3]
[0, 1, 2, 2, 3, 3, 4]
[0, 1, 2, 2, 3, 4, 4]
[0, 0, 0, 0, 0, 0, 0]
[0, '↑', '↑', '↑', '↖', '←', '↖']
[0, '↖', '←', '←', '↑', '↖', '←']
[0, '↑', '↑', '↖', '←', '↑', '↑']
[0, '↖', '↑', '↑', '↑', '↖', '←']
[0, '↑', '↖', '↑', '↑', '↑', '↑']
[0, '↑', '↑', '↑', '↖', '↑', '↖']
[0, '↖', '↑', '↑', '↑', '↖', '↑']
其最长公共子序列长度为: 4
其最长公共子序列为:
B
C
B
A