1. 程式人生 > >最大子序列、最長遞增子序列、最長公共子串、最長公共子序列、字串編輯距離總結

最大子序列、最長遞增子序列、最長公共子串、最長公共子序列、字串編輯距離總結

一、最大子序列

即找出由陣列成的一維陣列中和最大的連續子序列。例如{5, -6, 4, 2}的最大子序列是{4, 2},它們的和是6。


思路:假設陣列為num,用dp[i]儲存當遍歷到num[i]時,num[0]~num[i]之間求得的最大子序列的和。

遍歷num,當遍歷到num[i]時,轉換方程如下:

如果dp[i-1]>0,則dp[i] = dp[i-1] + num[i],

否則dp[i] = num[i]。


這麼去想,站在num[i]的角度來看,如果dp[i-1]>0,說明前i-1個數的最大子序列和大於0,那麼num[i]加上一個正數肯定會比num[i]大,因此dp[i] = dp[i-1] + num[i]。如果dp[i-1]<=0,那麼num[i]加上一個非正數肯定比num[i] 小或者相等,所以dp[i] = num[i]


c++程式碼

#include<iostream>
using namespace std;
int main(){
	int num[6]={5, -3, -4, 12, 9, -1};
	int len=6;
	int* dp = new int[6];
	int max_len=0;
	if(len>0){
		dp[0] = num[0];
		for(int i=1;i<len;i++){
			if(dp[i-1]>0){
				dp[i] = dp[i-1] + num[i];
			}else{
				dp[i] = num[i];
			}
			if(dp[i]>max_len)
				max_len = dp[i];
		}
	}
	cout<<"最大子序列和為:"<<max_len<<endl;
	return 0;
		
	
}


輸出:

最大子序列和為:21

二、最長遞增子序列

給定一個長度為N的陣列,找出一個最長的單調自增子序列(不一定連續,但是順序不能亂)。例如:給定一個長度為6的陣列A{5, 6, 7, 1, 2, 8},則其最長的單調遞增子序列為{5,6,7,8},長度為4.


程式碼:

#include<iostream>
using namespace std;

int maxLen(int arr[], int length){
    int* dp = new int[length+1];
    int max = 0;
    for(int i=0;i<length;i++){
        dp[i] = 1;
        for(int j=0;j<i;j++){
            if(arr[j]<arr[i] && (dp[j]+1)>dp[i]){
                dp[i] = dp[j]+1;
            }
        }//for
        if(dp[i] > max){
            max = dp[i];
        }
    }//for

    return max;
}

int main(){
    int arr[8] = {1, -1, 2, -3, 4, -5, 6, -7};
    cout<<maxLen(arr, 8);
    return 0;
}


測試用例:

在序列1,-1,2,-3,4,-5,6,-7中,其最長的遞增子序列為1,2,4,6

給定一個長度為6的陣列A{5, 6, 7, 1, 2, 8},則其最長的單調遞增子序列為{5,6,7,8},長度為4


三、最長公共子串和最長公共子序列

最長公共子串(Longest Common Substring)是串的一個連續的部分。最長公共子序列(Longest Common SubsequenceLCS)不一定是連續的,但是不改變序列中元素在原串中的相對順序,而是從序列中去掉任意的元素獲得新的序列。也就是說,子串中字元的位置必須是連續的,子序列則可以不連續。


(1)最長公共子序列

思想

採用動態規劃的思想,假設比較字串s1s2的最長公共子序列,len1,len2分別為s1s2的長度,L[m][n]表示當s1[m]s2[n]比較完之後得到的最長公共子序列長度。有如下轉換方程:

如果s1[i] == s2[j],那麼L[m][n] = L[m-1][n-1] + 1

如果s1[i] != s2[j],那麼L[m][n] = max(L[m-1][n], L[m][n-1])

 

例子

以騰訊2017年實習生招聘的這道題為例

題目:

構造迴文

給定一個字串s,你可以從中刪除一些字元,使得剩下的串是一個迴文串。如何刪除才能使得迴文串最長呢?
輸出需要刪除的字元個數。

輸入描述:

輸入資料有多組,每組包含一個字串s,且保證:1<=s.length<=1000.

輸出描述:

對於每組資料,輸出一個整數,代表最少需要刪除的字元個數。

輸入例子:

abcda
google

輸出例子:

2
2

思路:

根據迴文串的特點,如果一個字串是迴文串,那麼這個字串和它的逆序串是相等的。所以上述問題轉化為求字串和逆序字串的最長公共子序列長度。

c++程式碼

#include<iostream>
#include<cmath>
#include<algorithm>
using namespace std;
int main(){
	string s;
	while(cin>>s){
			
			string s1=s;
			string s2=s;
			reverse(s2.begin(),s2.end());
			int len1=s1.length();
			int len2=s2.length();
			
			//動態申請一個二維陣列
			//因為題目給出了s.length<=1000,因此L的長度可以固定
			//直接定義L[1001][1001]也可以,比動態申請陣列更方便 
			int **L = new int* [len1+1];
			int i,j;
			for(i=0; i<=len1;i++){
				L[i] = new int[len2+1];
			} 
			for(i=0;i<=len1;i++){
				for(j=0;j<=len2;j++)
					L[i][j]=0;  //對陣列進行初始化 
			}//for
			
			for(i=1;i<=len1;i++){
				for(j=1;j<=len2;j++){
					if(s1[i-1] == s2[j-1]){
						L[i][j] = L[i-1][j-1] + 1;
					}else{
						L[i][j] = max(L[i-1][j], L[i][j-1]);
					}
				}
			}
			
			cout<<len1-L[len1][len2]<<endl;
	}
	

	
	return 0;
	
}


例2:

題目描述:怎麼判斷一個字串是對稱的?如果不對稱,求最少需要新增幾個元素讓其變為對稱的?(2017年秋招CVTE面試題)


思路:對稱其實就是迴文,第一問簡單就不說了,第二問和上面例子其實差不多,對於一個不對稱的字串str,求它與它的逆序的最長公共子序列x,然後用長度len-x,即為要新增的元素數


(2)最長公共子串

思路和最長公共子串差不多,也是用動態規劃的方法。唯一要改變的一個地方是轉換方程那,改成:

如果s1[i] == s2[j],那麼L[m][n] = L[m-1][n-1] + 1

如果s1[i] != s2[j],那麼L[m][n] = 0


因為要求是連續的,因此只要有一個不相等的出現,那麼就把L[m][n]置為0。


例子

來源:2017年校招全國統一模擬筆試(第二場)程式設計題集合-牛客網

連結:https://www.nowcoder.com/questionTerminal/276712b113c6456c8cf31c5073a4f9d7

時間限制:1秒空間限制:32768K

題目描述:

牛牛有兩個字串(可能包含空格),牛牛想找出其中最長的公共連續子串,希望你能幫助他,並輸出其長度。

輸入描述:

輸入為兩行字串(可能包含空格),長度均小於等於50.

輸出描述

輸出為一個整數,表示最長公共連續子串的長度

例項1

輸入

abcde

abgde

Sit it out G
Sit down and shut up

輸出

2

4


程式碼:

#include<iostream>
#include<stack>
#include<cmath>
#include<cstring>
#define MAX 55

using namespace std;

int dp[MAX][MAX];

int main() {
    char str1[MAX], str2[MAX];
    int len1, len2;
    while(cin.getline(str1, MAX)){
        cin.getline(str2, MAX);
        memset(dp, 0, sizeof(dp));
        len1 = strlen(str1);
        len2 = strlen(str2);
        if(len1 == 0 || len2 == 0){
            return 0;
        }//if
        int i,j,max_len=0;
        for(i=1;i<=len1;++i){
            for(j=1;j<=len2;++j){
                if(str1[i-1] == str2[j-1]){
                    dp[i][j] = dp[i-1][j-1] + 1;
                }
                if(dp[i][j] > max_len){
                    max_len = dp[i][j];
                }
            }//for
        }
        cout<<max_len<<endl;
    }
    return 0;

}

這道題要注意的是輸入的字串可能包含空格!!!因此不能用string了,要用char陣列結合cin.getline函式。


四、字串編輯距離

編輯距離,又稱Levenshtein距離,是指兩個字串之間,由一個轉成另一個所需的最少編輯操作次數。

允許的操作包括(1)刪除一個字元(2)插入一個字元(3)將一個字元改為另一個字元


利用動態規劃的思想。用一個二維陣列edit[i][j],表示第一個字串s1的長度為i的子串和第二個字串s2的長度為j的子串的編輯距離。那麼容易得到如下的轉化方程:

if i==0 且 j==0, edit(i,j) = 0

if i==0 且 j>0, edit(i, j) = j

if i>0 且 j==0, edit(i,j) = i

if i>0 且 j>0, edit(i,j) = min{ edit(i-1,j)+1, edit(i, j-1)+1, edit(i-1,j-1)+diff(i,j)}

其中diff(i,j)表示的是s1[i] 和s2[j] 是否相等,如果s1[i] == s2[j],那麼diff(i,j)=0,否則diff(i,j)=1


舉個栗子:

假設s1 = "sailn",s2="failing",len1,len2分別為s1和s2的長度,為了處理邊界的情況,一般在動態申請二維陣列edit的時候多申請一行和一列,變為edit[len1+1][len2+1]。


那麼初始化情況如下:



edit從[1][1]開始算,但是注意比較的s1和s2是從下標0開始。明白了思想後,寫出程式碼不難。


c++程式碼如下:

#include<iostream>
#include<cmath>
using namespace std;
int main(){
	string s1,s2;
	int i,j;
	//比較s1和s2的編輯距離
	while(cin>>s1>>s2){
		int len1=s1.length();
		int len2=s2.length();
		
		if(len1==0 && len2==0){
			cout<<0<<endl;
		}else if(len1==0 && len2>0){
			cout<<len2<<endl;
		}else if(len1>0 && len2==0){
			cout<<len1<<endl;
		}else{
			//動態申請一個edit[len1+1][len2+1]的二維陣列
			int **edit = new int*[len1+1];
			for(i=0;i<=len1;i++){
				edit[i] = new int[len2+1];
			}
		
			//對第一行和第一列進行初始化 
			edit[0][0] = 0;
			for(i=1;i<=len1;i++){
				edit[i][0] = i;
			}
			for(i=1;i<=len2;i++){
				edit[0][i] = i;
			}
			
			for(i=1;i<=len1;i++){
				for(j=1;j<=len2;j++){
					edit[i][j] = min(edit[i-1][j]+1, edit[i][j-1]+1);
					if(s1[i-1] == s2[j-1]){
						edit[i][j] = min(edit[i][j], edit[i-1][j-1]);
					}else{
						edit[i][j] = min(edit[i][j], edit[i-1][j-1]+1);
					}
				}
			}
			for(i=0;i<=len1;i++){
				for(j=1;j<=len2;j++)
					cout<<edit[i][j]<<" ";
				cout<<endl;
			}
			cout<<"編輯距離為:"<<edit[len1][len2]<<endl;
		}
	
	} //while
	
	return 0;
}

輸出如下: