最長遞增子序列的求解(O(n*n),O(nlogn))——動態規劃

該問題均使用動態規劃來解決

1.在O(n*n)的複雜度下求解最長遞增子序列


1) 描述最優子結構

序列中每個元素維護兩個額外信息量,均假設當前元素(i)是 (0~i-1)個元素子序列中作爲遞增子序列的最後一個元素,則維護當前元素最後最後一個元素的遞增子序列的長度,並維護該序列中當前元素的前一個元素。

如:
{1,5,6,7,3} 中,元素1 維護的信息量分別爲:

(1)從0~0的子序列中,1作爲最後一個元素的遞增子序列的長度。即是 {1} ,記做len(0)

(2)該元素的前一個遞增元素的下標,對第0號元素,初始前一個元素爲-1(結束條件),記做 prev(0)

類似的,對元素 5 維護的信息量爲:

(1) 2。以 5 作爲最後一個元素的遞增子序列的長度爲2 ,即有 {1,5}

(2)0。即當前元素的前一個遞增元素的下標:即值1的下標。所以爲0


則對第 i 個元素,要直到以第 i 個元素爲最後一個元素的最長遞增子序列的長度,應該遍歷 0 ~ i - 1 個元素,找到其中 val(k) < val(i) 且 len(k) 最大的 k值,將第 i 個元素接在第 k 個元素之後,形成一個新的以 i 個元素結尾的最長遞增子序列


2)遞歸解

根據上述描述,得到如下的遞歸式子,對於第 i 個元素, val(i) 描述該元素的值,len(i) 描述以該元素爲最後一個元素的最長遞增子序列的長度的。prev(i) 表示該遞增子序列的前一個元素的下標。則遞歸式如下:

len(i) =  max{len(j)}  +1, if j ∈(0, i - 1) 且 val( j ) < val( i ) 。選中的元素的下標記做k,則 prev( i ) = k


3)計算所有元素

對所有元素進行計算,得到以每個元素作爲遞增子序列的最後一個元素的最長遞增序列的長度。遍歷一次計算得到的結果,取記錄長度最大的元素爲結果,該元素就是整個序列中最長遞增子序列的最後一個元素。根據prev(k),能夠找出最長遞增子序列的倒數第二個元素,依次列推,直到找到 -1,此時找到的序列即爲最長遞增子序列


4)實現代碼

int longestAddSeq(vector<int> & seq)
{
	vector<pair<int, int>> s;   //pair 中的第一個元素,記錄len,第二元素記錄 prv
	int max_loc = -1,max_val = 0;
	for (size_t i = 0; i != seq.size(); ++i)
	{
		int val = seq[i];
		int l_max_loc(-1), l_max_val(0);
		for (int j = i - 1; j >= 0; --j)
		{
			if (seq[j] < val && s[j].first > l_max_val) //取前面的元素中小於當前元素,且len 最長的元素
			{
				l_max_loc = j;
				l_max_val = s[j].first;
			}
		}
		++l_max_val;
		s.emplace_back(make_pair(l_max_val, l_max_loc));
		l_max_val > max_val ? max_val = l_max_val, max_loc = i : 1;
	}

	int loc = max_loc;
	while (loc != -1)
	{
		cout << seq[loc] << "\t";
		loc = s[loc].second;
	}
	cout << endl;
	return s[max_loc].first;
}

2.在O(nlgn)的複雜度下求解最長遞增子序列


1) 描述最優子結構

維護長度爲 i 的遞增子序列的最後一個元素(只維護長度爲i 的遞增子序列中最小的最後一個元素)。

如:對序列{1,4,5,3,6,4}

① 遍歷第一個元素:1,此時維護長度爲 1 的最長子序列的最後一個元素爲1,記錄記做 1(長度)->1(長度爲1的序列的最小的最後一個元素)


② 遍歷第二個元素:4,比較記錄中的值,找到第一個小於4的元素爲1,則4接在1後將組成新的遞增子序列,長度爲2,則增加新紀錄,此時有記錄爲 1 -> 1 ,2 -> 4


③ 遍歷第三個元素:5,比較記錄中的值,找到第一個小於5的元素爲4,則5接在4後將組成新的遞增序列,長度爲3,則增加新的記錄。此時記錄爲 : 1 ->1,2 -> 4,3->5


④ 遍歷第四個元素:3,比較記錄中的值,找到第一個小於3的元素爲1,則3接在1後將組成新的遞增序列,長度爲2,最後一個元素爲3,小於記錄中當前長度爲2的序列的最後一個元素4,則更新長度爲2的遞增子序列的最後一個元素爲3。此時記錄更新爲:1 ->1,2 -> 3,3->5

⑤ 遍歷第五個元素:6,比較記錄中的值,找到第一個小於6的元素爲5,則6接在5後將組成新的遞增序列,長度爲4,則增加新的記錄。此時記錄爲:1 ->1,2 -> 3,3->5,4 -> 6

⑥ 遍歷第六個元素:4,比較記錄中的值,找到第一個小於4的元素爲3,則4接在3後將組成新的遞增序列,長度爲3,最後一個元素爲3,小於當前長度爲 3 的遞增子序列的最後一個元素5,則更新長度爲3的遞增子序列的最後一個元素爲4。此時記錄更新爲:1 ->1,2 -> 3,3->4,4 -> 6

2)遞歸解

根據上述描述,則當新加入一個元素(i)時,先檢查長度記錄表,找到表中值第一個小於當前值的元素(k),k元素作爲遞增子序列末尾的長度爲 s,該新元素接在 k 元素之後將組成一個新的遞增子序列,新遞增子序列的長度爲(s + 1),此時新的遞增子序列的最後一個元素爲 i。檢查記錄表中是否有長度爲 s + 1 的記錄。如果沒有,則將該元素與長度 s + 1 作爲新的記錄插入表中。若有,則比較新序列的最後一個元素 i 與記錄中記下的最後一個元素 i'。如果  i < i',則更新記錄。否則,不更新記錄。


對長度爲 n 的子序列,遞增子序列記錄的長度最多爲 n,(即每個長度維護一個記錄)。對每個元素,要在記錄中尋找第一個小於該元素的元素,根據記錄項的記錄方式,可以知道記錄中隨着長度的增加,記錄對應的值是遞增的,因此可用二分搜索找到第一個小於該元素的元素。長度爲O(lgn)。對每個元素均找到位置,並比較更新。而比較更新的時間爲 O(1)。因此時間複雜度爲O(nlgn)


3) 得到結果

最後記錄表中的最後一項即爲當前序列中長度最長的遞增子序列。同時也可以知道了遞增子序列的長度。如果需要獲得遞增子序列的具體元素。則將要採用如下的替換方式 ;

仍然是序列 {1,4,5,3,6,4}

第一個元素 1 : {1,(1)} 。前一項爲遞增子序列的長度,後一項爲子序列的組成元素集合

第二個元素 4 : {1,(1)},{2,(1,4)}

第三個元素 5 : {1,(1)},{2,(1,4)},{3,(1,4,5)}

第四個元素 3 : {1,(1)},{2,(1,3)},{3,(1,4,5)}

第五個元素 6 : {1,(1)},{2,(1,3)},{3,(1,4,5)},{4,(1,4,5,6)}

第六個元素 4 :  {1,(1)},{2,(1,3)},{3,(1,3,4)},{4,(1,4,5,6)}

則最長遞增子序列的長度爲 4,且子序列爲{1,4,5,6}


4)實現代碼

int find_loc(vector<int> & record, int val)
{
	int beg = 0;
	int end = record.size() - 1;

	while (beg < end)
	{
		int mid = beg + (end - beg) / 2;

		if (record[mid] <= val)
		{
			beg = mid + 1;
		}
		else
			end = mid;
	}
	if (beg >= record.size() || record[beg] <= val)
	{
		cout << "ops" << endl;
		return record.size();
	}
	return beg;
}

int advance_longestAddSeq(vector<int>&seq)
{
	vector<int> record;
	vector<vector<int>> path;

	for (int i = 0; i != seq.size(); ++i)
	{
		int loc = find_loc(record, seq[i]);
		if (loc < record.size())
		{
			record[loc] = seq[i];
			vector<int> & vec = path[loc];
			vec = path[loc - 1];
			vec.push_back(seq[i]);
		}
		else
		{
			record.push_back(seq[i]);
			vector<int> n;
			if (path.size() >= 1)
				n = path[path.size() - 1];
			else;
			n.push_back(seq[i]);
			path.push_back(n);
		}
	}
	for (auto v : path[path.size() - 1])
		cout << v << "\t";
	cout << endl;

	return record.size();
}



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