算法编程题积累(3)——腾讯笔试"构造回文“问题
首先理解题意,回文串的特点:倒序后跟原串相同。故而可以将原串看成向一个回文串在任意位置添加任意字符后形成的字符串,也就是说原串中存在一段未必连续的回文序列。
通过分析可以知道AC本题的核心思路:求出回文序列的长度,用原串的长度减去其长度即可。
要求出回文序列的长度,肯定要利用回文串的特点,故而想到求原串和其反串的最长公共子序列(不一定连续)。
所以可以采用文本比较算法中的Needleman/Wunsch算法(动态规划思想):
定义:
LCS(A,B)表示字符串A和字符串B的最长公共子串的长度。很显然,LSC(A,B)=0表示两个字符串没有公共部分。
Rev(A)表示反转字符串A
Len(A)表示字符串A的长度
A+B表示连接字符串A和字符串B
性质:
LCS(A,A)=Len(A)
LCS(A,"")=0
LCS(A,B)=LCS(B,A)
0≤LCS(A,B)≤Min(Len(A),Len(B))
LCS(A,B)=LCS(Rev(A),Rev(B))
LCS(A+C,B+C)=LCS(A,B)+Len(C)
LCS(A+B,A+C)=Len(A)+LCS(B,C)
LCS(A,B)≥LCS(A,C)+LCS(B,C)
LCS(A+C,B)≥LCS(A,B)+LCS(B,C)
为了讲解计算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
故: LCS(N,M)=LCS(A,B)
LCS(0,0)=0
LCS(0,j)=0
LCS(i,0)=0
对于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))=Max(LCS(i-1,j),LCS(i,j-1))
计算LCS(A,B)的算法有很多,下面介绍的Needleman/Wunsch算法是其中的一种。和LD算法类似,Needleman/Wunsch算法用的都是动态规划的思想。在Needleman/Wunsch算法中还设定了一个权值,用以区分三种操作(插入、删除、更改)的优先级。在下面的算法中,认为三种操作的优先级都一样。故权值默认为1。
举例说明:A=GGATCGA,B=GAATTCAGTTA,计算LCS(A,B)
第一步:初始化LCS矩阵
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 |
第二步:利用公式一,计算矩阵的第一行
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 |
第三步:利用公式一,计算矩阵的其余各行
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
--------------------------------------------------我是分割线---------------------------------------------------------------
AC代码如下:
1 #include <bits/stdc++.h> 2 using namespace std; 3 #define MAX 1100 4 5 int LCS[MAX][MAX]; 6 string s1, s2; 7 int i, j; 8 int maxOfAll(int a, int b, int c) 9 { 10 return max(max(a, b), c); 11 } 12 13 int main() 14 { 15 while(cin >> s2) 16 { 17 s1 = s2; 18 size_t len = s1.length(); 19 for(int ix = 0; ix <= len; ++ix) 20 LCS[ix][0] = LCS[0][ix] = 0; 21 reverse(s2.begin(), s2.end()); 22 23 for(i = 1; i <= len; ++i) 24 { 25 for(j = 1; j <= len; ++j) 26 { 27 if(s1[i-1] == s2[j-1]) 28 LCS[i][j] = LCS[i-1][j-1] + 1; 29 else 30 LCS[i][j] = maxOfAll(LCS[i-1][j], LCS[i-1][j-1], LCS[i][j-1]); 31 } 32 33 } 34 cout << len-LCS[len][len] << endl; 35 } 36 }
1 import java.util.*; 2 3 public class Main { 4 public static void main(String[] args) { 5 Scanner sc = new Scanner(System.in); 6 while(sc.hasNext()) { 7 String s1 = sc.next(); 8 String s2 = new StringBuffer(s1).reverse().toString(); 9 int [][] dp = new int[s1.length()+1][s2.length()+1]; 10 for (int i = 1; i < dp.length; ++i) { 11 for (int j = 1; j < dp[0].length; ++j) { 12 dp[i][j] = s1.charAt(i-1) == s2.charAt(j-1) ? dp[i-1][j-1] + 1 : Math.max(dp[i-1][j], dp[i][j-1]); 13 } 14 } 15 System.out.println(s1.length() - dp[s1.length()][s2.length()]); 16 } 17 sc.close(); 18 } 19 }