實在是汗顏,網上做一道題半天沒進展:
給定一個字符串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 }
以上代碼都很好明白,就解釋一下mutrix二位數組的作用,首先mutrix[m][n]表示第一個數組到m,第二個數組到n,這種情況下的最長子序列長度。如果沒有這個能計算嗎?可是可以,不過會導致類似用3行代碼計算斐波那契數列的問題,產生巨量的重復運算(使用遞歸)。同時mutrix的存在可以使得我們回溯出最長子序列的值