【DP】 最长公共回文子序列

本文详细介绍了如何通过动态规划算法计算给定字符串的最长回文子序列长度,并提供了Java代码实现。重点阐述了最优子结构的概念及其在解决回文子序列问题中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

问题描述:
给一个字符串,找出它的最长的回文子序列的长度。例如,如果给定的序列是“BBABCBCAB”,则输出应该是7,“BABCBAB”是在它的最长回文子序列。 “BBBBB”和“BBCBB”也都是该字符串的回文子序列,但不是最长的。


1)最优子结构

假设 X[0 ... n-1]  是给定的序列,长度为n.  让 L(0,n-1) 表示 序列 X[0 ... n-1] 的最长回文子序列的长度。

1. 如果X的最后一个元素和第一个元素是相同的,这时:L(0, n-1) = L(1, n-2) + 2 ,  还以 “BBABCBCAB” 为例,第一个和最后一个相同,因此 L(1,n-2) 就表示蓝色的部分。

2. 如果不相同:L(0, n-1) = MAX ( L(1, n-1) ,  L(0, n-2) )。 以”BABCBCA” 为例,L(1,n-1)即为去掉第一个元素的子序列,L(0, n-2)为去掉最后一个元素。




package 最长回文子序列;

import java.util.Scanner;

public class test {

	/**
	 * 计算个数的时候,比较适合自上而下计算;
	 * 但是求解具体的值,适合自下而上。
	 */
	//统计个数
//	public static int lhc(int start,int end){
//		if(start>=end)
//			return 0;
//		else{
//			if(ch[start]==ch[end]){
//				//他们之间最长子序列的长度加2
//				return 	lhc(start+1,end-1)+2;
//			}else{
//				//判断是[start+1,end],[start,end-1]之间的序列长度
//				return lhc(start+1,end)>lhc(start,end-1)?lhc(start+1,end):lhc(start,end-1);
//			}
//		}
//	}
	public void inti(int[][] lpsArray,int max){
		for(int i=0;i<max;i++)
			for(int j=0;j<max;j++)
				lpsArray[i][j]=0;
	}
	
	public int max(int x,int y){
		return x>y?x:y;
	}
	
	public char dir(int x,int y){
		return x>y?'←':'↓';
	}
	//创建数组
	public int[][] lps(char[] ch){
		int[][] lpsArray = new int[ch.length][ch.length];
		char[][] dirc = new char[ch.length+1][ch.length+1];
		this.inti(lpsArray,ch.length);
		//method 1.
		/*
		
		for(int i=0;i<ch.length;i++)
			for(int j=i;j<ch.length;j++){
				if(i==j)
					lpsArray[i][j]=0;
				else{
					if(ch[i]==ch[j])
						lpsArray[i][j]=1;
					else
						lpsArray[i][j]=0;
				}
			}
		*/
		//method 2.
		for(int i=0;i<ch.length;i++)
			lpsArray[i][i]=0;
		
		for(int i=1;i<ch.length;i++){
			int temp =0;
			//开始计算ch[i]到ch[j]之间最长回文的个数
			for(int j=0;j+i<ch.length;j++){
				if(ch[j]==ch[j+i]){
						temp = lpsArray[j+1][j+i-1]+2;
						dirc[j][j+i]='√';
				}else{
						temp = max(lpsArray[j][i+j-1],lpsArray[j+1][j+i]);
						dirc[j][j+i] = dir(lpsArray[j][i+j-1],lpsArray[j+1][j+i]);
				}
				lpsArray[j][i+j]=temp;
			}
		}
		for(int i=0;i<ch.length;i++){
			for(int j =0;j<ch.length;j++)
				System.out.print(" "+dirc[i][j]+"  ");
			System.out.println();
		}
		this.showSeq(dirc,lpsArray,ch.length);
		return lpsArray;
	}
	
	//输出序列
	public void showSeq(char[][] dirc,int[][] lpsArray,int max){
		 int i = 0;  int j = max-1;
		 while(lpsArray[i][j]!=0){
			 System.out.println(dirc[i][j]);
			 if(dirc[i][j]=='←'){
				 j--;
			 }else if(dirc[i][j]=='↓'){
				 i++;
			 }else{
				 System.out.println("i = "+i+" j="+j);
				 i++;
				 j--;
			 }
		 }
	}
	//正
	public static void main(String[] args) {
		test t = new test();
		// TODO Auto-generated method stub
		System.out.println("请输入任意的字符串");
		Scanner s =  new Scanner(System.in);
		String str = s.next();
		//将字符串变为char数组
		char[] ch = new char[str.length()] ;
		str.getChars(0, str.length(), ch,0);
		//存储最优解
		int[][] lps =t.lps(ch);
		
		for(int i=0;i<ch.length;i++){
			for(int j =0;j<ch.length;j++)
				System.out.print(lps[i][j]+"  ");
			System.out.println();
		}	
	}

}



### 计算最长回文子序列的动态规划方法 要解决这个问题,可以采用 **动态规划 (Dynamic Programming)** 的方式来求解。以下是详细的分析和实现: #### 动态规划的核心思路 设 `dp[left][right]` 表示字符串从索引 `left` 到 `right` 能够构成的最长回文子序列的最大长度。 1. 如果字符串两端字符相同 (`s[left] == s[right]`),则它们可以作为回文的一部分,因此有: \[ dp[left][right] = dp[left+1][right-1] + 2 \] 2. 如果字符串两端字符不同,则需要分别考虑去掉左端或右端的情况,取两者中的最大值: \[ dp[left][right] = \max(dp[left+1][right], dp[left][right-1]) \] 最终目标是计算整个字符串范围内的最长回文子序列长度,即 `dp[0][n-1]`,其中 `n` 是字符串的长度[^2]。 --- #### 边界条件 当子串只有一个字符时(即 `left == right`),显然这个单字符本身就是一个回文子序列,所以有: \[ dp[i][i] = 1, \quad \text{对于所有的 } i \] 当子串为空时(即 `left > right`),此时不存在有效的子序列,故其长度为零。 --- #### 时间复杂度与空间复杂度 时间复杂度为 \(O(n^2)\),因为我们需要填充一个大小为 \(n \times n\) 的二维数组。 空间复杂度同样为 \(O(n^2)\),用于存储中间状态的结果。 --- #### Python 实现代码 下面是基于上述理论的具体实现代码: ```python def longest_palindromic_subsequence(s: str) -> int: n = len(s) if n == 0: return 0 # 创建 DP 数组并初始化 dp = [[0] * n for _ in range(n)] # 单个字符都是回文,长度为1 for i in range(n): dp[i][i] = 1 # 填充 DP 表格 for length in range(2, n + 1): # 子串长度从2到n for left in range(n - length + 1): right = left + length - 1 if s[left] == s[right]: dp[left][right] = dp[left + 1][right - 1] + 2 else: dp[left][right] = max(dp[left + 1][right], dp[left][right - 1]) # 返回整个字符串的最长回文子序列长度 return dp[0][n - 1] # 测试案例 if __name__ == "__main__": test_string = "bbbab" result = longest_palindromic_subsequence(test_string) print(f"输入: {test_string}, 输出: {result}") # 应输出4 ``` 此代码实现了动态规划算法,并通过测试用例验证了正确性[^3]。 --- #### 结果解释 以上程序会输出给定字符串 `"bbbab"` 中最长回文子序列的长度为 `4`,这对应于子序列 `"bbbb"`。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值