最長單增子序列問題

      問題描述:有一個長爲n的數列a0,a1,a2........a(n-1)。請求出這個序列中最長的單增子序列的長度。單增子序列的定義是:對於任意的 i<j,都滿足ai<aj。

      這個問題就是著名的最長單增子序列(LIS)問題。對於這道問題,我們可以利用動態規劃來進行求解:假設dp[i]表示以a[i]爲末尾的最長單增子序列的長度,則在得到dp[i]時,我們可以這樣做:初始化dp[i]爲1,利用一個j變量遍歷已經訪問過的數組a中的值,如果此時a[i]>a[j],表示我們可以在原來的子序列之後加上一個構成一個新的以ai結尾的單增子序列,這時,如果dp[i]的值小於dp[j]+1的值時,我們就將其更新。這樣我們就可以得到:時間複雜度爲O(n^2)。

                         dp[i] = max{dp[j] + 1, 1} if j<i and a[j] < a[i]

#include<iostream>
#define max(a, b) ((a)>(b)?(a):(b))

const int INF = 1000000;
const int n = 6;
int a[n] = {4, 2, 3, 1, 5, 5};

int dp[n];

/**
dp[i]表示以a[i]爲末尾的最長單增子序列的長度
*/
int dp1(){
	int res = 0;
	for (int i = 0; i < n; i++){
		dp[i] = 1;
		for (int j = 0; j < i; j++)
		{
			if (a[i] > a[j])
			{
				dp[i] = max(dp[i], dp[j]+1);
			}
		}
		res = max(dp[i], res);
	}

	return res;
}

int main(){
	printf("%d\n", dp1());
	system("pause");
	return 0;
}
       接下來我們換一種思路來想,如果子序列的長度相同的話,那麼取得的末尾值越小就越有優勢。基於這種思路我們可以利用這樣的假設:dp[i]表示長度爲i+1的上升子序列中末尾元素的最小值。首先我們對dp數組用無窮大INF進行初始化,對於每一個ai,如果j==0,或者dp[j-1]<a[i],我們就用dp[j] = min(dp[j], a[i])來進行更新。這樣我們仍然需要一個兩層的循環,但是可以進行優化,對於內層的循環:由於dp數組是單增的(可以用反證法證明,如果i<j,而dp[i] > dp[j],則在以dp[j]結尾的單增子序列中的第i位一定小於dp[i],這樣就違背了當初對dp數組的假設),我們可以使用一個二分搜索來找的我們需要更新的位置,即在dp數組中找到大於或等於a[i]且最近的位置,這樣就將時間複雜度降低爲O(n*logn)。

#include<iostream>
#define max(a, b) ((a)>(b)?(a):(b))

const int INF = 1000000;
const int n = 6;
int a[n] = {4, 2, 3, 1, 5, 5};

int dp[n];

/*
返回dp數組中找到>=target且最近的位置
*/
int binary_search(int target){
	int l = -1, r = n, m;

	while(l+1 != r){
		m = (l+r)/2;
		if (dp[m] < target)
			l = m;
		else
			r = m;
	}
	return r;
}

/**
dp[i]表示長度爲i+1的上升子序列中末尾元素的最小值
*/
int dp2(){

	for (int i = 0; i < n; i++)
	{
		dp[i] = INF;
	}
	for (int i = 0; i < n; i++)
	{
		int p = binary_search(a[i]);
		dp[p] = a[i];
	}

	return binary_search(INF);
}

int main(){
	printf("%d\n", dp2());
	system("pause");
	return 0;
}


      
發佈了45 篇原創文章 · 獲贊 10 · 訪問量 18萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章