最長子序列

轉自:點擊打開鏈接

最長子序列可以說是剛接觸動態規劃的人經常遇見也不得不解決的問題,最常見的有兩種,一種是最長公共子序列(LCS),還有一個是最長上升子序列(LIS)。今天我就總結下這兩個的做法。


一:最長公共子序列(LCS)
  題目描述:給你兩個數組,可以是數字的,也可以是字符串,我們假設是數字的!舉個例子:

    X  =  1, 5, 6, 4, 1, 3, 7

    Y  =  1, 1, 6, 8, 3, 4, 7

  求一個新的數組S,該數組中的每個數均是X和Y數組中的公共數,並滿足原數組中數字的前後關係,這樣的數組有很多個,比如說(1,1),(1,1,3,7),(1,6,7)等。同時S數組要是長度最長的那個,像上面的(1,1,3,7),長度是4,那麼即爲所求解!

  做DP題目最重要的一點是能正確的構造出DP函數,如果能很好的構造出來,就成功一半了。

  dp[i][j] 表示X數組的前i位和Y數組的前j位之前的LCS,那麼它可以由前面三個狀態推出來。

   0                                    if(i=0 || j=0) --初始化,不難理解,不管是X還是Y數組,只要有一個長度是0,那麼S數組就是0

   dp[i][j] = max(dp[i-1][j],dp[i][j-1]    if(i,j>0 && X[i] != Y[j]) --S數組沒有添加數字,那麼只能從前面繼承來,一個從X,一個從Y,看那個大

   dp[i-1][j-1] + 1              if(i,j>0 && X[i] == Y[j]) --如果是兩個相同,當然是把S數組加1,可以看成X和Y都繼承了

   代碼很簡單,兩個for循環就可以了。

二:最長遞增子序列(LIS)


  題目描述:給你一個數組,如X數組,X = 1,2,3,6,5,7,4,8,6。

  求一個新的數組S,該數組中的每個數均是X數組中的數,且對S中任意兩個數S[i],S[j]滿足 j>i && s[j]>s[i]。同時S數組要是長度最長的那個,像(1,2,3,6,7,8)和(1,2,3,5,7,8),長度是6,即爲所求解。

  dp[i]表示以X數組中的第i個元素爲S數組的底元素的最長子序列的長度。這樣又可以變成子問題來求解了,

  dp[i] = max(dp[j]) + 1    0<j<i  &&  X[i] > num[j]。就是在i前面找一個符合條件的最長子序列。

    對這個題目來說,dp方程還是比較容易推出來和理解的,但你可以發現,時間複雜度是O(n^2)的,這對許多題目都是不能接受的,所以你必須進行優化。這就牽扯到一個問題,最長遞增子序列的O(n*lgn)做法。

    做法如下:首先給你一個數組stack,用來存儲最長遞增子序列(按照遞增的要求,即數組的最頂端的數最大,最尾的數最小,如果說用棧的話你也許更好理解),對X數組進行線掃,如果X[i]大於數組的頂端元素(最大值),那麼把這個數加入到stack數組中來,如果比頂端元素小,那麼在stack數組中找到第一個大於X[i]的數,並用X[i]替換掉。因爲stack數組是有序的,在你找第一個大於X[i]的數的時候你就可以用二分查找來做,這就是優化所在!


    這兩個都是很簡單的dp題目,但也包含了動態規劃思想,在後面的很多題目中你會發現都會不經意中用到這種思想,所以很好的理解他們是必須的。但動態規劃博大精深,就因爲他沒有固定的程序,許多都是思想,所以僅僅知道這兩個是遠遠不夠的!


O(n^2)的算法:

#include <iostream>
#include <cstdio>
#include <cstring>
#include <climits>
using namespace std;
const int maxn = 1010;
int n;
int array[maxn], maxv[maxn], lis[maxn];
int main(){
    scanf("%d", &n);
	for(int i = 1; i <= n; i++){
		scanf("%d", &array[i]);
		lis[i] = 1;
	}
	lis[0] = -1;

	maxv[1] = array[1];
	maxv[0] = INT_MIN;
	int maxlis = 1;

	for(int i = 1; i <= n; i++)
	{
		int j;//mav[i]表示長度爲i的最長遞增子序列中的最大元素中的最小值
		for(j = maxlis; j >= 1; j--)
		{
			if(array[i] > maxv[j])
			{
				lis[i] = j + 1;
				break;
			}
		}
		if(lis[i] > maxlis){
			maxlis = lis[i];
			maxv[maxlis] = array[i];
		}
		else if(maxv[j] < array[i] && array[i] < maxv[j + 1])
		{
			maxv[j + 1] = array[i];
		}
	}
	printf("%d\n", maxlis);
		return 0;
}

for(j = maxlis; j >= 0; j--)
  {
   if(array[i] > maxv[j])
   {
    lis[i] = j + 1;
    break;
   }
  }
在求那個j時,改用二分搜索查詢,可以把O(n^2)降爲O(nlogn)

#include <iostream>
#include <cstdio>
#include <cstring>
#include <climits>
using namespace std;

const int maxn = 1010;
int n;
int array[maxn], maxv[maxn], lis[maxn];

int binarysearch(int low, int high, int index);

int main()
{
	scanf("%d", &n);
	for(int i = 1; i <= n; i++)
	{
		scanf("%d", &array[i]);
		lis[i] = 1;
	}
	lis[0] = -1;
	maxv[1] = array[1];
	maxv[0] = INT_MIN;
	int maxlis = 1;
	for(int i = 1; i <= n; i++)
	{ 
		int j = binarysearch(1, maxlis, i);//找到前面長度最長的並且滿足最後一個元素小於array[i]。
		if(j != -1)
			lis[i] = j + 1;
		else
			j = 0;
		if(lis[i] > maxlis)
		{
			maxlis = lis[i];
			maxv[maxlis] = array[i];
		}
		else if(maxv[j] < array[i] && array[i] < maxv[j + 1])
		{
			maxv[j + 1] = array[i];
		}
	}
	printf("%d\n", maxlis);
		return 0;
}

int binarysearch(int low, int high, int index)
{
	int pre = -1;
	while(low <= high)
	{
		int mid = (low + high) >> 1;
		if(array[index] > maxv[mid])
		{
			pre = mid;
			low = mid + 1;
		}
		else
			high = mid - 1;
	}
	return pre;
}




發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章