算法分析与设计,第二周博客
Longest Palindromic Subsequence
Given a string s, find the longest palindromic subsequence's length in s. You may assume that the maximum length of s is 1000.
Example 1:
Input:
"bbbab"
Output:
4
One possible longest palindromic subsequence is "bbbb".
Example 2:
Input:
"cbbd"
Output:
2
One possible longest palindromic subsequence is "bb".
本题是要找出一个字符串的最长回文子序列的长度。本篇博客建立在上一篇博客的基础上,只讲解两者的区别。如果您还没阅读上一篇博客,那么请您最好先阅读上一篇博客( LeetCode-Longest Palindromic Substring),以便更好的理解。
先了解子序列的概念:字符串从某处开始到某处结尾,选取其中的若干字符组成的新字符串,称为该字符串的子序列。
子序列的特点是:子序列中存在的字符,在原字符串中一定能找出至少一个与之相对应的字符,且整个字符串的排布顺序也符合原字符串的顺序。
那么子序列和子串的区别是:子串必定是原字符串中连续的部分,而子序列可以不是连续的。
在了解了这两道题目的不同之处后,再次回到这道题目上。对于这题,依然可以用动态规划的思路来解答。那么,就需要理清楚这之间的关系。
用p[i, j]来表示以i为开始、j为结尾的子序列的最长回文长度。显然,我们需要返回的就是p[0, n-1]。那么,p[i, j]又有着什么样的性质呢?
- 首先,最简单直观的属性:p[i,j] >= p[m,n] if i <= m && j >= n,一个包含了更短的子序列的最长回文长度一定会不小于这个更短的子序列的最长回文长度。这也就是为什么最终返回p[0, n-1]的原因。
- if (s[i] == s[j] && j-i <= 2) p[i,j] = j-i+1; 这个是回文的性质,上篇博客里也有介绍,就不做更多的解释了。
- 根据性质1,我们知道,p[i,j] >= both(p[i+1,j],p[i,j-1]) >= p[i+1,j-1] ;那么:
- if (s[i] != s[j]) p[i,j] = max(p[i+1,j], p[i,j-1]); 因为开始处和结尾处的字符不一致,那么所组成的回文字符串便不可能同时包括了开始处和结尾处,所以就分成两种情况,就把首尾分别去除一个,把得到的子串进行比较,留下较长的那一个。
- if (s[i] == s[j]) p[i,j] = max(2+p[i+1,j-1], p[i+1,j], p[i,j-1]); 和上一种情况类似,只是需要多考虑一种情况,就是因为首尾字符相等,可能把首尾都去除掉的子串的最大长度加上首尾两个字符的长度比之前两种情况更加的长,所以需要把这种情况也考虑进去。其实实际上,在这种情况下,p[i,j] = 2+p[i+1,j-1],而不需哟比较其他两个情况了,因为 2+p[i+1,j-1] = 1+ (1+p[i+1,j-1]); 而1+p[i+1,j-1] >= both(p[i,j+1], p[i+1,j]),即 父字符串的最大长度 <= 子字符串的最大长度+父子字符串的长度差。所以,2+p[i+1,j-1]一定是这三者中最大的那一个。
有了以上几个性质,以后,就足以用动态规划来处理这道题目了,所实现的代码如下:
class Solution {
public int longestPalindromeSubseq(String s) {
int n = s.length();
if (n == 0)
return 0;
int[][] p = new int[n][n];
for (int i = 0; i < n; ++i)
p[i][i] = 1;
for (int i = n-2; i >= 0; --i) {
for (int j = i+1; j < n; ++j) {
if (s.charAt(i) == s.charAt(j) && i >= j-2)
{
p[i][j] = j-i+1;
}
int one = p[i][j-1];
int two = p[i+1][j];
p[i][j] = Math.max(one, two);
if (s.charAt(i) == s.charAt(j)) {
int three = 2+p[i+1][j-1];
p[i][j] = Math.max(p[i][j] , three);
}
}
}
return p[0][n-1];
}
}
很容易就能看出来,这个算法的时间复杂度是O(n^2)。