算法导论15.2 最长回文子序列 Longest palindrome subsequence

本文介绍了一种高效算法用于查找给定输入字符串中的最长回文子序列,详细阐述了三种实现方法,并提供了代码实现。

算法导论15.2 最长回文子序列,注意其子序列可以是不连续的。

15-2 Longest palindrome subsequence
A palindrome is a nonempty string over some alphabet that reads the same forward
and backward. Examples of palindromes are all strings of length 1, civic, refer,
racecar, and aibohphobia (fear of palindromes).

Give an efficient algorithm to find the longest palindrome that is a subsequence
of a given input string. For example, given the input character, your algorithm
should return carac. 

主要有三种算法:

1. 把当前串A逆序转换为另外一个串B,把问题转换为求这两个串的最长公共子序列问题,O(n^2)

2.直接用动态规划方法求此字符串的最长回文子序列,O(n^2)

3.使用Manacher算法求最长回文序列,O(n)回文子串算法,但Manacher算法只能计算出下标连续的回文子串,例如characher返回最大字串长度为3(ara),而不是5(carac)。

参考了网上的资料,第二种方法大多给出了长度,没有给出最长回文串,下面是我写的方法2的代码。

  public static void main(String[] args) {
    longestPalindromeSubseq("cib");       //c
    longestPalindromeSubseq("biic");      //ii
    longestPalindromeSubseq("civic");      //civic
    longestPalindromeSubseq2("beneab");    //beneb
    longestPalindromeSubseq("character"); //carac
    longestPalindromeSubseq("abacdfgdcaba"); //abacdfdcaba
  }

/*
设字符串为s,f(i,j)表示s[i..j]的最长回文子序列。 最长回文子序列长度为f(0, s.length()-1)
状态转移方程如下:
当i>j时,f(i,j)=0。 
当i=j时,f(i,j)=1。 
当i<j并且s[i]=s[j]时,f(i,j)=f(i+1,j-1)+2。 
当i<j并且s[i]≠s[j]时,f(i,j)=max( f(i,j-1), f(i+1,j) )。 
注意如果i+1=j并且s[i]=s[j]时,f(i,j)=f(i+1,j-1)+2=f(j,j-1)+2=2,这就是当i>j时f(i,j)=0的好处。
 */
  public static String longestPalindromeSubseq(String str) { // O(n^2)
    if(str == null || str.length()<=1) {
      return str;
    }
    System.out.println(Arrays.toString(str.toCharArray()));
    int n = str.length();
    String[] resultStrs = new String[n]; //index+1 is palindrome subseq with length index+1
    resultStrs[0] = "" + str.charAt(0); // initialize to first char for length is 1

    int[][] matrix = new int[n][n]; //default value is 0 

    for(int j=0; j<n-1; j++) {
      matrix[j][j] = 1;     // f(j,j)=1; f(j,j-1)=0, same as default value
    }
    int startIndex = 0;
    int endIndex = 0;
    int maxLen = 1;
    for(int i=n-1; i>=0; i--) {
      for (int j = i+1; j < n; j++) {
        // i<j case
          if (str.charAt(i) == str.charAt(j)) {
            int preLen = matrix[i + 1][j - 1];
            matrix[i][j] = 2 + preLen;
            if (maxLen <= matrix[i][j]) {
              maxLen = matrix[i][j];
              StringBuilder sb = new StringBuilder("");
              if(preLen==1) {
                sb.append(str.charAt(i));
                sb.append(str.charAt(i+1)); // or j-1
                sb.append(str.charAt(j));
              }
              else if(preLen==0) {
                sb.append(str.charAt(i));
                sb.append(str.charAt(j));
              }
              else {
                sb.append(str.charAt(i));
                sb.append(resultStrs[preLen-1]);
                sb.append(str.charAt(j));
              }
              resultStrs[maxLen-1] = sb.toString();
              startIndex = i;
              endIndex = j;
            }
          }
          else {
            matrix[i][j] = Math.max(matrix[i + 1][j], matrix[i][j - 1]);
          }
      }
    }
    System.out.println("startIndex:"+startIndex + ", endIndex:"+endIndex);
    System.out.println("max length:"+maxLen);
    System.out.println("sub string:" + str.substring(startIndex, endIndex+1));
    String longestSubSeq = null;
    for (int i = 0; i < n; i++) {
      if (resultStrs[i] != null && resultStrs[i].length() == maxLen) {
        longestSubSeq = resultStrs[i]; // find the longest from n-1
        break;
      }
    }
    System.out.println("longest palindrome subseq:" + longestSubSeq);
    return longestSubSeq;
  }

### Java 实现最长回文子序列动态规划算法 以下是使用 Java 编写的最长回文子序列的动态规划算法示例。代码逻辑基于动态规划的思想,通过二维数组 `dp` 存储子问题的结果,逐步构建最终解。 ```java public class LongestPalindromicSubsequence { public static int longestPalindromeSubseq(String s) { int n = s.length(); // 创建 dp 数组 int[][] dp = new int[n][n]; // 单个字符的情况 for (int i = 0; i < n; i++) { dp[i][i] = 1; } // 构建 dp 表格 for (int len = 2; len <= n; len++) { // 子串长度 for (int i = 0; i <= n - len; i++) { // 起始索引 int j = i + len - 1; // 结束索引 if (s.charAt(i) == s.charAt(j)) { dp[i][j] = dp[i + 1][j - 1] + 2; // 如果两端字符相等 } else { dp[i][j] = Math.max(dp[i + 1][j], dp[i][j - 1]); // 如果两端字符不相等 } } } // 返回整个字符串的最长回文子序列长度 return dp[0][n - 1]; } public static void main(String[] args) { String s = "cbbd"; System.out.println("最长回文子序列长度: " + longestPalindromeSubseq(s)); // 输出: 2 } } ``` #### 代码解析 - **初始化**:`dp[i][i] = 1`,表示单个字符的最长回文子序列长度为 1。 - **状态转移方程**: - 当 `s[i] == s[j]` 时,`dp[i][j] = dp[i+1][j-1] + 2`[^1]。 - 当 `s[i] != s[j]` 时,`dp[i][j] = Math.max(dp[i+1][j], dp[i][j-1])`[^1]。 - **边界条件**:确保 `i <= j`,避免无效索引。 - **结果**:`dp[0][n-1]` 存储了整个字符串的最长回文子序列长度。 #### 示例运行 对于输入字符串 `"cbbd"`: - 初始状态:`dp[i][i] = 1`。 - 动态规划计算后,最终结果为 `dp[0][3] = 2`,即最长回文子序列是 `"bb"`。 --- ###
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值