1. 程式人生 > >經典演算法——最長迴文子序列

經典演算法——最長迴文子序列

最長迴文子序列LPS(Longest Palindromic Subsequence)問題

一個字串有許多子序列,比如字串cabbeaf,它的子序列有c、abb、e、a、f,可以通過刪除某些字元而變成迴文字串,字串“cabbeaf”,刪掉‘c’、'e'、‘f’後剩下的子串“abba”就是迴文字串,也是其中最長的迴文子序列。注意和最長迴文子串點選開啟連結的區別,最長迴文子串必須是連續的,這裡的最長迴文子序列,可以是不連續的,這就是最長迴文子序列LPS問題。

方法一:遞迴方法

str[0...n-1]是給定的字串序列,長度為n,假設lps(0,n-1)表示序列str[0...n-1]的最長迴文子序列的長度。

1.如果str的最後一個元素和第一個元素是相同的,則有:lps(0,n-1)=lps(1,n-2)+2;例如字串序列“AABACACBA”,第一個元素和最後一個元素相同,其中lps(1,n-2)表示紅色部分的最長迴文子序列的長度;

2.如果str的最後一個元素和第一個元素是不相同的,則有:lps(0,n-1)=max(lps(1,n-1),lps(0,n-2));例如字串序列“ABACACB”,其中lps(1,n-1)表示去掉第一元素的子序列,lps(0,n-2)表示去掉最後一個元素的子序列。



#include<iostream>
#include<string>
#include<algorithm>
using namespace std;

//遞迴方法,求解最長迴文子序列
int lps(char *str, int i, int j)
{
	if (i == j)
		return 1;	//只有一個元素,迴文長度為1
	if (i > j) return 0;   //因為只計算序列str[i....j]

	//如果首尾相同
	if (str[i] == str[j])
		return lps(str, i + 1, j - 1) + 2;
	//如果首尾不同
	return max(lps(str, i, j - 1), lps(str, i + 1, j));
}

int main()
{
	char str[] = "cabbeaf";
	int n = strlen(str);
	int res = lps(str, 0, n - 1);
	cout << res<< endl;
	getchar();
	return 0;
}

重疊子問題


但是通過上面遞迴的方法,會出現很多重複的計算,比如上面的L(1,4),所以可以採用動態規劃的方法求解

方法二:動態規劃方法

通過自下而上的方式記錄子問題的最優解


#include<iostream>
#include<algorithm>
using namespace std;

//動態規劃求解最長迴文子序列,時間複雜度為O(n^2)
int lpsDp(char *str, int n)
{
	int dp[10][10], tmp;
	memset(dp, 0, sizeof(dp));
	for (int i = 0; i < n; ++i) 	dp[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 - 1] + 2;
			//如果首尾不同
			else 
				tmp = max(dp[j + 1][j + i], dp[j][j + i - 1]);
			dp[j][j + i] = tmp;
		}
	}
	return dp[0][n - 1]; //返回字串str[0...n-1]的最長迴文子序列長度
}

int main()
{
	char str[10] = "cabbeaf";
	int res = lpsDp(str, strlen(str));
	cout << res << endl;
	getchar();
	return 0;
}


參考:http://www.geeksforgeeks.org/dynamic-programming-set-12-longest-palindromic-subsequence/

附:騰訊2016實習筆試程式設計題

所謂迴文字串,就是一個字串,從左到右讀和從右到左讀是完全一樣的,比如“aba”、“c”,對於一個字串,可以通過刪除某些字元而變成迴文字串,如“cabebaf”,刪除'c'、'e'、‘f’後剩下子串“abba”就是迴文字串。

要求,給定任意一個字串,字串最大長度1000,計算出最長的迴文字串長度。

如“cabebaf”的迴文串包括“c”、“aba”、“abba”等,最長迴文“abba”長度為4。

輸入:字串

輸出:最大的迴文字元長度。

示例:

輸入:cabbeaf

輸出:4

#include<iostream>
#include<algorithm>
using namespace std;
#define MAXN 1000

//動態規劃求解最長迴文子序列,時間複雜度為O(n^2)
int lpsDp(char *str, int n)
{
	int dp[MAXN>>1][MAXN>>1], tmp;
	memset(dp, 0, sizeof(dp));

	//字串長度為1,最長迴文子序列的長度就是1
	for (int i = 0; i < n; ++i) 	dp[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 - 1] + 2;
			//如果首尾不同
			else 
				tmp = max(dp[j + 1][j + i], dp[j][j + i - 1]);
			dp[j][j + i] = tmp;
		}
	}
	return dp[0][n - 1]; //返回字串str[0...n-1]的最長迴文子序列長度
}


int main()
{
	char str[MAXN];
	while (cin >> str)
	{
		int res = lpsDp(str, strlen(str));
		cout << res << endl;
	}
	getchar();
	return 0;
}