最長遞增子序列:
找到給定序列的最長子序列的長度,使得子序列所有元素單調遞增。
解法一 轉化爲求最長公共子序列
- 設數組{3,5,7,1,2,8}爲A
- 對數組A排序,排序後爲B={1,2,3,5,7,8}。
- 求A數組的最長遞增子序列,就是求數組A與數組B的最長公共子序列
最長公共子序列求法的時間複雜度:
解法二 動態規劃法
與第一個辦法不同的是,動態規劃是直接在原問題上採用動態規劃
最優子結構:
對於長度爲N的數組 ,假設我們想要以 結尾的最大遞增子序列長度,設長度 ,那麼
也就是 的範圍是 到 。這樣,想求 結尾的最大遞增子序列的長度,我們就需要遍歷 之前的所有位置 ,找出 ,計算這些 中,能產生最大 ,之後就可以求出 。之後對每一個 中的元素都計算以他們各自結尾的最大遞增子序列長度,這些長度的最大值,就是我們要求的問題數組 的最大遞增子序列的長度
重疊問題:
根據上面公式的推導採用遞歸實現的話,有一些子問題就會被計算很多次。
動態規劃法:
綜上所述:LIS問題具有動態規劃需要的兩個性質,可以使用動態規劃求解該問題。設數組 ,則:
具體打表方式如下:
- 初始化對角線爲 1;
- 對每一個 :
- 若 ,置 1。
- 若 ,取第 行的最大值加 1。
打完表以後,最後一行的最大值就是最長遞增子序列的長度。由於每次都進行遍歷,故時間複雜度還是 。
//LIS 的動態規劃方式實現
include <iostream>
using namespace std;
int getLISLength(int A[], int len) {
//定義一維數組並初始化爲1
int* lis = new int[len];
for (int i = 0; i < len; ++i)
lis[i] = 1;
// 計算每個i對應的lis最大值,即打表的過程
for (int i = 1; i < len; ++i)
for (int j = 0; j < i; ++j) // 0到i-1
if ( A[i] > A[j] && lis[i] < lis[j] + 1)
lis[i] = lis[j] + 1; // 更新
// 數組中最大的那個,就是最長遞增子序列的長度
int maxlis = 0;
for (int i = 0; i < len; ++i)
if ( maxlis < lis[i] )
maxlis = lis[i];
delete [] lis;
return maxlis;
}
解法三 的方案
本解法的具體操作如下:
- 建立一個輔助數組array,依次讀取數組元素 x 與數組末尾元素 top比較:
- 如果 x > top,將 x 放到數組末尾;
- 如果 x < top,則二分查找數組中第一個 大於等於x 的數,並用 x 替換它.
遍歷結束之後,最長遞增序列長度即爲棧的大小。
注意c數組的下標代表的是子序列的長度,c數組中的值也是按遞增順序排列的。這纔可能用二分查找。
int getLISLength(int num[], int length) {
vector<int> ivec;
for (int i = 0; i < length; ++i) {
if (ivec.size() == 0 || ivec.back() < num[i])
ivec.push_back(num[i]);
else {
int low = 0, high = ivec.size() - 1;
while (low < high) {
int mid = (low + high) / 2;
if (ivec[mid] < num[i])
low = mid + 1;
else
high = mid - 1;
}
ivec[low] = num[i];
}
}
return ivec.size();
特別注意的是:本方法只能用於求最長遞增子序列的長度,輔助數組中的序列不是最長遞增子序列:
- 例一:原序列爲1,5,8,3,6,7
輔助數組爲1,5,8,此時讀到3,用3替換5,得到1,3,8; 再讀6,用6替換8,得到1,3,6;再讀7,得到最終棧爲1,3,6,7。最長遞增子序列爲長度4。- 例二:原序列爲1,5,8,3
則最棧輔助數組爲1,3,8。明顯這不是最長遞增子序列!
合唱隊問題
問題描述:
計算最少出列多少位同學,使得剩下的同學排成合唱隊形
問題說明:
位同學站成一排,音樂老師要請其中的 位同學出列,使得剩下的K位同學排成合唱隊形。
合唱隊形是指這樣的一種隊形:設 位同學從左到右依次編號爲 他們的身高分別爲 則他們的身高滿足存在 使得 。
任務是:
已知所有N位同學的身高,計算最少需要幾位同學出列,可以使得剩下的同學排成合唱隊形。
輸入:
整數 ,一行整數,空格隔開, 位同學身高
輸出:
最少需要幾位同學出列
樣例輸入:8 186 186 150 200 160 130 197 200
樣例輸出:
4
根據題意可知,我們需要求出一個“中間點”,使得其左邊的【最長遞增子序列】和其右邊的【最長遞減子序列】之和最大。
```
#include <iostream>
#include <vector>
using namespace std;
int LonggestIncreaseLength(vector<int> &vec) {
vector<int> result(vec.size(), 1);
vector<int> result2(vec.size(), 1);
for (int i = 1; i < vec.size(); i++) {
for (int j = 0; j < i; j++) {
if (vec[i] > vec[j] && result[i] < result[j] + 1)
result[i] = result[j] + 1;
}
}
for (int i = vec.size() - 2; i >= 0; --i) {
for (int j = vec.size() - 1; j > i; --j) {
if (vec[i] > vec[j] && result2[i] < result2[j] + 1)
result2[i] = result2[j] + 1;
}
}
int max = 0;
for (int i = 0; i < vec.size(); i++) {
if (max < result[i] + result2[i])
max = result[i] + result2[i];
}
return vec.size() - max + 1;
}
int main() {
int n;
cin >> n;
if (n <= 0)
return 0;
vector<int> ivec(n);
for (int i = 0; i < n; i++)
cin >> ivec[i];
cout << LonggestIncreaseLength(ivec) << endl;
}