最近遇到兩個題目,比較有意思,由於兩個題目的描述比較相似,在這里就一起說了,做一個比較
題目一:給定一個字符串,給該字符串添加一些字符,使其成為一個回文串,求需要添加的最少字符數,並求出添加字符后回文串的樣子,如果有多個這樣的回文串,只用返回其中一個即可
比如: str="AB" 那么,只用在 "A" 之前添加一個B,就可以形成回文 “ABA”
str="A" 那么,不用添加,就已經是回文了
str="ACDC" , 那么在最后添加一個A就可以形成回文 "ACDCA"
思路:此題可以用動態規划進行操作,開一個數組dp[i][j], 表示要想使得 i 到 j 這段長度的字符串成為回文字符串,至少需要添加多少個字符,我們都知道動態規划需要分析通項公式,但是有一些不需要依賴其他
元素,可以獨立推出來的值,我們需要單獨處理:當i=j, 即字符串的長度為1時,這個字符串肯定是回文的,當字符串長度大於2時,有如下情況
(1)如果 i 和 j 相鄰 , 並且str[i]==str[j], 那么dp[i][j]=0, 如果 str[i] != str[j] ,那么dp[i][j]=1;表示需要添加一個字符串
(2)如果 i 和 j 不相鄰, 但str[i]==str[j], 那么dp[i][j]=dp[i+1][j-1],表示只要 i 和 j 內部的字符串組成回文后,i 到 j 這部分就自然成為回文了 ,如果 str[i] != str[j] ,那么dp[i][j]=min(dp[i][j-1],dp[i+1][j])+1, 表示只要單邊形成回文串后,另一邊添加對邊的值即可成為回文,此時有兩種補法,所以需要取最小的
注意到上面動態規划時,dp[i][j] = dp[i+1][j-1]等表達式,隱含的條件是,外面的值需要依賴里面的值,所以我們在填表時,需要注意由里面向外面擴散
求出dp表以后,再按照兩邊向中間的順序進行還原,就可以還原出回文字符串了,比如,當str[left] 和 str[right] 相等時,直接復制到copy[ ] 數組中去,如果str[left] 和 str[right] 不相等,那么就比較dp[left][right-1] 和 dp[left][right-1] 的值,看看補哪邊需要的字符串最少,具體代碼如下
1 import java.util.ArrayList; 2 import java.util.Deque; 3 import java.util.LinkedList; 4 import java.util.Scanner; 5 import java.util.Stack; 6 /*給定字符串,添加最少成為回文字符串,采用動態規划*/ 7 public class Main { 8 public static void main(String[] args) { 9 Scanner scan = new Scanner(System.in); 10 String str = scan.nextLine(); 11 char s[] = str.toCharArray(); 12 int dp[][] = new int[s.length][s.length]; //dp[i][j] 表示 i 到 j 成為回文字符需要添加的最少字符數量 13 for (int j=1;j<s.length;j++) { //結尾坐標,依次遞增 14 for (int i=j-1;i>=0;i--) { //由內向外擴散 15 if (s[i]==s[j]) { 16 if (i==j-1) dp[i][j]=0; 17 else dp[i][j] = dp[i+1][j-1]; 18 }else { 19 dp[i][j] = Math.min(dp[i][j-1], dp[i+1][j])+1; 20 } 21 } 22 } 23 char res[] = new char[s.length+dp[0][s.length-1]]; //最終需要的長度 24 int i=0;int left=0; 25 int j=s.length-1; 26 int right=res.length-1; 27 while(i<=j) { 28 if (s[i]==s[j]) { 29 res[left++]=s[i++]; 30 res[right--]=s[j--]; 31 }else { 32 if (dp[i][j-1]<dp[i+1][j]) { 33 res[left++]=s[j]; 34 res[right--]=s[j--]; 35 }else { 36 res[left++]=s[i]; 37 res[right--] = s[i++]; 38 } 39 } 40 } 41 for (int k=0;k<res.length;k++) { 42 System.out.print(res[k]); 43 } 44 } 45 }
題目二:給定一個字符串,給該字符串刪除一些字符,使其成為一個回文串,求需要刪除的最少字符數,並求出刪除字符后回文串的樣子,如果有多個這樣的回文串,只用返回其中一個即可
比如 str="A" 那么不用刪除任何子串,就可形成回文“A”
str="AB" 那么可以刪除“A” ,就可以形成回文“B”
str="ABAD" 那么可以刪除“D” 就可以形成回文“ABA”
第二道題目雖然題目和第一題很像,但解法卻不一樣,可以采用倒轉原字符串,形成新的字符串str2,然后再求str1與str2的最長公共子序列即可,求最長公共子序列需要開一個二維數組dp[i][j],表示str1的前 i個字符和str2的前 j個字符的最長
公共子序列,如果i 和 j 相等, 那么 dp[i][j] = dp[i-1][j-1]+1, 如果 i 和 j 不相等,那么取dp[i][j-1] 和 dp[i-1][j] 兩個字串的長度中選一個最大的,具體代碼如下:
1 import java.util.*; 2 /*給定字符串,刪除最少成為回文字符串,采用動態規划*/ 3 public class Main { 4 public static void main(String[] args) { 5 Scanner scan = new Scanner(System.in); 6 String str = scan.nextLine(); 7 char s1[] = str.toCharArray(); 8 char s2[] = reverse(str.toCharArray()); 9 int dp[][] = new int [str.length()][str.length()]; // 記錄最長子序列 10 boolean flag=false; 11 //對第0行單獨處理 12 for (int j=0;j<s2.length;j++) { 13 if (flag || s1[0]==s2[j]) { 14 dp[0][j]=1; 15 flag=true; 16 } 17 } 18 flag=false; 19 //對第0列單獨處理 20 for (int j=0;j<s1.length;j++) { 21 if (flag || s1[j]==s2[0]) { 22 dp[j][0]=1; 23 flag=true; 24 } 25 } 26 //求DP矩陣 27 for (int i=1;i<s1.length;i++) { 28 for(int j=1;j<s2.length;j++) { 29 if (s1[i]==s2[j]) { 30 dp[i][j] = dp[i-1][j-1]+1; 31 }else if (dp[i-1][j]>dp[i][j-1]) { 32 dp[i][j] = dp[i-1][j]; 33 }else { 34 dp[i][j]=dp[i][j-1]; 35 } 36 } 37 } 38 39 //反推最長子序列 40 int len = dp[s1.length-1][s2.length-1]; 41 int i = s1.length-1; 42 int j=s2.length-1; 43 while(len>0) { 44 if (i>=1&&dp[i][j]==dp[i-1][j]) { 45 i--; 46 }else if (j>=1 && dp[i][j]==dp[i][j-1]) { 47 j--; 48 }else { 49 System.out.print(s1[i]); //與前面的不等,說明這是最長子序列里面的元素,輸出 50 len--; 51 i--; 52 j--; 53 } 54 } 55 } 56 public static char[] reverse(char c[]) { 57 int i=0; 58 int j=c.length-1; 59 while(i<j) { 60 char temp = c[i]; 61 c[i++]=c[j]; 62 c[j--]=temp; 63 } 64 return c; 65 } 66 }