動的プログラミング問題の例(3):最長の共通部分列/文字列、最長の回文部分列/部分文字列

注:サブシーケンスは連続している必要はありません。サブストリングは連続している必要があります。
1.最長共通サブ
シーケンス 2つのシーケンスを指定して、2つのシーケンスの最長共通サブシーケンスを見つけます。例:
ここに画像の説明を挿入
1.1。最適サブ構造:
ここに画像の説明を挿入
1.2。疑似コード:
ここに画像の説明を挿入
ここに画像の説明を挿入
マトリックスcおよびbの可視化:
ここに画像の説明を挿入
1.3。C ++コード:

#include <iostream>
#include <vector>
#include <string>
#include <stack>
using namespace std;

string getLCS(string s1, string s2)
{
    
    
//此部分获得最长值
	 int m=s1.length()+1;
	 int n = s2.length()+1;
	 vector< vector<int> > c(m, vector<int>(n)); //计算长度
	 vector< vector<int> > b(m, vector<int>(n)); // 字串遍历
	 for(int i = 0;i<m;i++) {
    
     c[i][0] = 0; } // 初始条件(边界条件)
	 for(int i = 0;i<n;i++) {
    
     c[0][i] = 0; } // 同上
	 for(int i = 0;i<m-1;i++){
    
    
		for(int j = 0;j<n-1;j++){
    
    
			if(s1[i] == s2[j]) {
    
     
				c[i+1][j+1] = c[i][j] + 1; 
				b[i+1][j+1] = 1; // 1 表示相同 
			}
			else if(c[i][j+1]>c[i+1][j]){
    
    
				c[i+1][j+1] = c[i][j+1];
				b[i+1][j+1] = 2; // 2 表示不同 
			}
			else{
    
    
				c[i+1][j+1] = c[i+1][j];
				b[i+1][j+1] = 3; //  3表示不同 
			}
		}
	}
//此部分获得公共子序列
	stack<char> LCSans;
	for(i = m-1,j = n-1; i>=0 && j>=0; ;){
    
    
		if(b[i][j] == 1){
    
    
			LCSans.push_back(s1[i]);
			i--;
			j--;
		}
		else if(b[i][j] == 2)
            i--;
        else
        	j--;
	}
// 此部分输出子序列
	while(!LCSans.empty()) {
    
    
       cout<<LCSans.top();
       LCSans.pop();
   }	 
}

参照:最長共通サブシーケンス(LCS)の動的プログラミングソリューション

2.最も長い共通部分文字列:最長の部分列判断文を
置き換えますelse if

string getLCS(string str1, string str2) {
    
    
	int m=s1.length()+1;
	int n = s2.length()+1;
	int maxlen = 0;
	int end;
	vector< vector<int> > c(m, vector<int>(n)); //计算长度
	for(int i = 0;i<m;i++) {
    
     c[i][0] = 0; } // 初始条件(边界条件)
	for(int i = 0;i<n;i++) {
    
     c[0][i] = 0; } // 同上
	for(int i = 0;i<m-1;i++){
    
    
		for(int j = 0;j<n-1;j++){
    
    
			if(s1[i] == s2[j]) {
    
     
				c[i+1][j+1] = c[i][j] + 1; 
			}
			else {
    
    
				c[i+1][j+1] = 0;
			}
			if(c[i+1][j+1]>maxlen){
    
    
				maxlen = c[i+1][j+1];
				end = i;
			}
		}
	}
	return str1.substr(end - maxlen + 1, maxlen);
}

参照:2つの文字列の最も長い共通部分文字列を見つける

3.最長の回文サブシーケンス:
文字列の文字は対称的であり、回文と呼ばれます。たとえば、文字列wabcdcbwqのwbcdcbwは回文サブシーケンス、bcdcbは回文サブシーケンスです。
解決策:
文字列sを反転してs 'に変更し、一般的なサブシーケンス法を使用して解決します。

string longestPalindrome(string s) {
    
    
    if(s.length()==1) return s;//大小为1的字符串必为回文串
    string rev=s;//rev存放s反转结果
    string res;//存放结果
    std::reverse(rev.begin(),rev.end());
    if(rev==s) return s;
	//从此开始调用最长公共子序列算法。

4.最も長い回文部分文字列:
4.1。動的プログラミングバージョン:
Sは文字列を表し、iとjは開始位置と終了位置を表します。
初期条件(境界条件):長さが2未満の場合、dp(i + 1、j -1)の範囲が間違っているため、長さは1、2が境界条件です
a)dp [i] [i] = true(コードで使用) 1の意味)
b)S [i] = S [i + 1]の場合、dp [i] [i + 1] = true
状態遷移方程式:
a)If(dp(i + 1、j -1)== true && S [i] == S [j])then dp(i、j)= true
b)if(dp(i + 1、j -1)== false || S [i]!= S [j ])次にdp(i、j)= false

class Solution {
    
    
public:
    string longestPalindrome(string s) {
    
    
        int len=s.size();
        if(len==0||len==1)
            return s;
        int start=0;//回文串起始位置
        int max=1;//回文串最大长度
        vector<vector<int>>  dp(len,vector<int>(len));//定义二维动态数组
        for(int i=0;i<len;i++)//初始化状态
        {
    
    
            dp[i][i]=1; //单个元素是回文串
            if(i<len-1&&s[i]==s[i+1]) //两个元素相同也是回文串
            {
    
    
                dp[i][i+1]=1;
                max=2;
                start=i;
            }
        }
        for(int l=3;l<=len;l++)//l表示检索的子串长度,等于3表示先检索长度为3的子串
        {
    
    
            for(int i=0;i+l-1<len;i++)
            {
    
    
                int j=l+i-1;//终止字符位置
                if(s[i]==s[j]&&dp[i+1][j-1]==1)//状态转移
                {
    
    
                    dp[i][j]=1;
                    start=i;
                    max=l;
                }
            }
        }
        return s.substr(start,max);//获取最长回文子串
    }
};

参考資料:最長回文部分文字列のC ++ソリューション3:動的プログラミング

4.2。中央拡張法:
中央拡散法でテキスト文字列を取得する方法?
それぞれの位置から始めて、両側に広げます。回文ではないときに終了します。たとえばstr=acdbbdaa、最初のb(位置3)から始まる最長の回文を見つける必要があります。見つけ方?
まず、不平等に遭遇するまで、左側の現在の位置と同じ文字を探します。
次に、右に移動して、現在の位置と同じ文字を探します。
最後に、左と右が等しくなくなるまで両方向に広がります。次の図に示すように、
ここに画像の説明を挿入
ウィンドウサイズ(len)が両側に広がる各位置に表示されます。If len>maxLen(最長の回文の長さを示すために使用されます)。更新されたmaxLen値。
最後に返すのは、長さではなく特定の部分文字列であるため、この時点で、開始位置(maxStart)をmaxLen、つまりmaxStart = lenに記録する必要もあります。

string LPS(string s)
{
    
    
	int maxstart = 0;
	int maxlen = 1;
	int start = 0;
	int len = 1;
	if(s.size()<=1) return s;
	for(int i = 0;i<s.size();i++){
    
    
		int l = i--;
		int r = i++;
		while(l>=0 && s[l] == s[i]) {
    
     start = l; len++; l--}
		while(r<s.size() && s[r] == s[i]) {
    
     len++; r++}
		while(l>=0 && r<s.size() && s[l] == s[r]) {
    
     
			start= l;
			l--;
			r++;
			len = len+2;
		}
		if(len > maxlen) {
    
     maxstart  = start; maxstart = start;}
		len = 1;
	}
	return s.substring(maxstart,maxlen);
}

参考資料:中央拡散法

総括する:

1.動的プログラミングの鍵:最適な部分構造を見つける、つまり初期状態と状態遷移方程式を見つける。

おすすめ

転載: blog.csdn.net/qq_33726635/article/details/105933862