子序列解题模板:最长回文子序列

子序列解题模板:最长回文子序列

一. 前言

一般来说,这类问题都是让你求一个最长子序列。一旦涉及到子序列和最值,考察的是动态规划技巧,时间复杂度一般都是 O(n^2)。

原因很简单,你想想一个字符串,它的子序列有多少种可能?起码是指数级的吧,这种情况下,不用动态规划技巧,还想怎么着呢?

既然要用动态规划,那就要定义 dp 数组,找状态转移关系。我们说的两种思路模板,就是 dp 数组的定义思路。不同的问题可能需要不同的 dp 数组定义来解决。

二. 两种思路

  1. 第一种思路模板是一个一维的dp数组:
int n = array.length;
int []dp = new int[n];

for(int i =1;i<n;i++){
	for(int j=0;j<i;j++){
		dp[i] = 最值(dp[i],dp[j]+...)
	}
}

举个最长递增子序列的例子,这个思路中dp数组的定义是:

在子数组array[0…i]中,以array[i]结尾的目标子序列(最长递增子序列)的长度是dp[i]。

  1. 第二种思路模板是一个二维的dp数组:
int n = arr.length;
int[][] dp = new dp[n][n];

for (int i = 0; i < n; i++) {
    for (int j = 1; j < n; j++) {
        if (arr[i] == arr[j]) 
            dp[i][j] = dp[i][j] + ...
        else
            dp[i][j] = 最值(...)
    }
}

涉及两个字符串/数组时(比如最长公共子序列),dp 数组的含义如下:

在子数组arr1[0…i]和子数组arr2[0…j]中,我们要求的子序列(最长公共子序列)长度为dp[i][j]。

只涉及一个字符串/数组时(比如本文要讲的最长回文子序列),dp 数组的含义如下:

在子数组array[i…j]中,我们要求的子序列(最长回文子序列)的长度为dp[i][j]。

三.最长回文子序列

在这里插入图片描述
首先回顾一下,对单个字符串的最长回文子序列问题,对dp数组的定义是: 在子串s[i…j]中,最长回文子序列的长度为dp[i][j]。

这样定义二维的dp数组的原因是,找状态转移需要归纳的思维,说白了就是如何从已知结果推出未知部分。

具体来说,如果知道了子问题dp[i+1][j-1]的结果:
对于字符s[i]和s[j] , 如果它俩相等,那么它俩加上s[i+1…j-1]中的最长回文子序列就是s[i…j]的最长回文子序列;如果它俩不相等,说明它俩不可能同时出现在s[i…j]的最长回文子序列中,那么把它俩分别加入s[i+1…j-1]中,看看哪个子串产生的回文子序列更长即可。

最后我们要求的实际上是dp[0][n-1],也就是整个s的最长回文子序列的长度。

四.代码实现

首先明确base case, 如果只有一个字符,显然最长回文子序列长度是1,也就是dp[i][j] =1(i==j);
对于i>j,根本不存在什么子序列,因此初始化为0。
再看状态转移方程,想求dp[i][j]需要知道dp[i+1][j-1],dp[i+1][j],dp[i][j-1]这三个位置;根据base case,填入数组后应为:
在这里插入图片描述
反着遍历:

int longestpal(String s){
	int n =s.length();
	int [][] dp = new int[n][n];
	Arrays.fill(dp,0);
	for(int i =0;i<n;i++)
		dp[i][i]=1;
	//反着遍历保证正确的状态转移
	for(int i = n-1;i>=0;i--){
		for(int j=i+1;j<n;j++){
			//状态转移方程
			if(s[i]==s[j])
				dp[i][j] = dp[i+1][j-1]+2;
			else
				dp[i][j] = Math.max(dp[i][j-1],dp[i+1][j]);
		}
	}
	return dp[0][n-1];
}

五.总结

主要还是正确定义 dp 数组的含义,遇到子序列问题,首先想到两种动态规划思路,然后根据实际问题看看哪种思路容易找到状态转移关系。

另外,找到状态转移和 base case 之后,一定要观察 DP table,看看怎么遍历才能保证通过已计算出来的结果解决新的问题。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值