【问题1】求字符串回文子序列的个数
问题:
给定字符串,求它的回文子序列个数。回文子序列反转字符顺序后仍然与原序列相同。例如字符串aba中,回文子序列为”a”, “a”, “aa”, “b”, “aba”,共5个。内容相同位置不同的子序列算不同的子序列。
分析与解法:
(注意:与子串不同,子序列可以是不连续的,只要元素的前后相对位置不变。)
解法一:递归
假设s[0 … n-1]是给定的序列,长度为让 c(0,n-1)表示序列s[0 … n-1] 的回文子序列的个数。
如果s的最后一个元素和第一个元素是相同的:c(1, n-1) + c(0, n-2) + 1。
如果s的最后一个元素和第一个元素是不同的,这时:c(0, n-1) = c(1, n-1) + c(0, n-2) - c(1, n-2) ,即”ab”+ “ba” - “b” 。
可以写出如下递归程序:
int count(string str, int i, int j)//调用时count(str, 0, n-1);
{
if(i == j)//一个元素即是它本身
return 1;
if(i > j)//只计算顺序时的子序列
return 0;
if(str[i] != str[j])//若首尾不同
return count(str, i + 1, j) + count(str, i, j - 1) - count(str, i + 1, j - 1);
else//若首尾相同
return count(str, i + 1, j) + count(str, i, j - 1) + 1;
}
解法二:动态规划
解法一中存在重复计算的问题,且当字符串长度过大时,递归的时间复杂度会很高。可以采用动态规划的方法。
用dp[j][j+i]表示s[j…j+i]之间的回文子序列的个数,
- 如果s的最后一个元素和第一个元素是相同的:dp[j][j+i] = dp[j+1][j+i] + dp[j][j+i-1] + 1。
- 如果s的最后一个元素和第一个元素是不同的,这时:dp[j][j+i] = dp[j+1][j+i] + dp[j][j+i-1] - dp[j+1][j+i-1],即”ab”+ “ba” - “b”。
这里可以将二维数组dp的值列出,方便理解。其中橙色区域的数值只与黄色区域的数值有关,以dp[0][1]为例,若首尾元素相同,则等于dp[1][1] + dp[0][0] + 1;若首尾元素不同,则等于dp[1][1] + dp[0][0] - dp[1][0]。
int count(string str,int n)
{
int dp[MAXSIZE][MAXSIZE], tmp;
memset(dp,0,sizeof(dp));//dp数组中所有元素置0
for(int i=0; i<n; i++) dp[i][i] = 1;//一个元素即是它本身
//i表示当前长度为i+1的子序列
for(int i=1; i<n; i++)
{
tmp = 0;
//考虑所有连续的长度为i+1的子串. 该串为 str[j, j+i]
for(int j=0; j+i<n; j++)
{
if(str[j] == str[j+i])//若首尾相同
tmp = dp[j+1][j+i] + dp[j][j+i-1] + 1;
else//若首尾不同
tmp = dp[j+1][j+i] + dp[j][j+i-1] - dp[j+1][j+i-1];
dp[j][j+i] = tmp;
}
}
//返回串 str[0][n-1] 的结果
return dp[0][n-1];
}
【问题2】求字符串回文子序列的最大长度
http://www.acmerblog.com/longest-palindromic-subsequence-5721.html