先要搞明白:最长公共子串和最长公共子序列的区别。
最长公共子串(Longest Common Substirng):连续
最长公共子序列(Longest Common Subsequence,LCS):不必连续
实在是汗颜,网上做一道题半天没进展:
给定一个字符串s,你可以从中删除一些字符,使得剩下的串是一个回文串。如何删除才能使得回文串最长呢?
输出需要删除的字符个数。
首先是自己大致上能明白应该用动态规划的思想否则算法复杂度必然过大。可是对于回文串很难找到其状态和状态转移方程,换句话说就是:某个序列是不是回文串和这个序列的子序列没有明显关系,一个序列是回文串再加上一个字母可能就不是了。所以其状态很难找。
最近没有太多时间,不能在一个问题上闭门造车(后来来看其实也对,这种问题是计算机系的课堂例题,单纯自己苦想比较难想到)。
思路是:反序这个字符串,求这个新串和原串的最大子序列。abcda--->adcba 最大子序列是aca,再相减就是最少的字符删除个数。所以问题变成了求两个字符串的最长子序列。
那么怎么求两个字符串的最长子序列呢?既然是用动态规划,最重要的是确定状态和状态转移方程。
状态:当str1的下标为m,str2的下标是n的时候(不考虑后面的),此时的最长子序列长度L。
转移方程:1,如果str1(m)==str2(n),那么L(m,n)=L(m-1,n-1)+1。2,如果str1(m)!=str2(n),那么L(m,n)=max(L(m-1,n),L(m,n-1))
解释一下,假如m和n相等,那么这个时候最长子序列无疑是前一个L(m-1,n-1)加上1,因为这两个字符串这个地方的字符都可以加入到最长子序列里面去。如果不相等,那么要么舍弃新来的来自str1的那个字符m号,要么舍弃str2的n号字符(最长子序列每个位置上当然都是唯一确定的一个字符),舍弃之后呢,就从
L(m-1,n),L(m,n-1)当中挑一个好的(能更长的)为当前状态的最长子序列。
代码:
1 import java.util.Scanner; 2 3 public class Main { 4 public static void main(String[] args) { 5 Solution s = new Solution(); 6 Scanner sc = new Scanner(System.in); 7 while(sc.hasNextLine()) { 8 System.out.println( s.getResult(sc.nextLine()) ); 9 } 10 sc.close(); 11 } 12 } 13 14 class Solution { 15 public int getResult(String s) { 16 StringBuilder s1 = new StringBuilder(s); 17 StringBuilder s2 = new StringBuilder(s).reverse(); 18 return s.length() - LCS(s1, s2); 19 } 20 public int LCS(StringBuilder s1, StringBuilder s2) { 21 int m = s1.length(); 22 int n = s2.length(); 23 int[][] mutrix = new int[m + 1][n + 1]; 24 25 for(int i = 1; i <= m; i++) { 26 for(int j = 1; j <= n; j++) { 27 if(s1.charAt(i - 1) == s2.charAt(j - 1)) 28 mutrix[i][j] = mutrix[i - 1][j - 1] + 1; 29 else 30 mutrix[i][j] = Math.max(mutrix[i - 1][j], mutrix[i][j - 1]); 31 } 32 } 33 return mutrix[m][n]; 34 } 35 }