1. 題目描述
給定一個長度為N的數列,求數值嚴格單調遞增的子序列的長度最長是多少。
輸入格式
第一行包含整數N。
第二行包含N個整數,表示完整序列。
輸出格式
輸出一個整數,表示最大長度。
資料範圍
\(1≤N≤1000\),
\(−10^9≤數列中的數≤10^9\)
輸入樣例:
7
3 1 2 1 8 5 6
輸出樣例:
4
2. (DP)樸素解法\(O(n^2)\)
本題是一個簡單的DP問題。
令\(a[i]\)表示陣列中第\(i\)個數,\(f[i]\)表示以陣列中第\(i\)個數結尾的最長上升子序列的長度。
則\(f[i] = max(f[j]) + 1,j < i 且a[j] < a[i]\)
程式碼如下:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int f[N], a[N];
int n;
int main()
{
cin >> n;
for(int i = 1; i <= n; i ++) scanf("%d", &a[i]);
int res = 0;
for(int i = 1; i <= n; i ++)
{
// 注意初始化f[i] = 1.
f[i] = 1;
for(int j = 1; j < i ; j ++)
if(a[j] < a[i])
f[i] = max(f[i],f[j] + 1);
res = max(res, f[i]);
}
cout << res << endl;
return 0;
}
3. (DP+貪心)優化版本\(O(nlgn)\)
通過對本題的觀察與思考。我們可以得到如下的事實:
- 對於兩個長度相同的子序列,假設兩個子序列的最後一個值分別是\(x_1, x_2\),且\(x_1 > x_2\)。假設我們的\(a[i]\)可以放在最後一個值為\(x_1\)的序列的後面,則其一定能夠放在最後一個值為\(x_2\)的序列的後面。故,對於長度相同的序列,我們只需要儲存最後一個值小的序列即可。
當我們掃描到第\(i\)個數的時候,可以將其前面的數構成的子序列按長度進行分類,長度為\(1\)的最長上升子序列只儲存結尾值最小的,長度為\(2\)的最長上升子序列只儲存結尾值最小的,那麼我們可以證明:不同長度最長上升子序列最後一個數的值是隨長度嚴格單調遞增的。
證明:
假設長度為\(5\)的最長上升子序列最後一個數為\(a\),長度為\(6\)的最長上升子序列最後一個數為\(b\),倒數第二個數為\(c\)。
反證法:如果\(a>=b\),由於\(b > c\),則\(a>c\)。又由於對於長度相同的子序列我們只儲存最後一個數值較小值。故\(a< c\)。推出矛盾。
這樣的話,當我們掃描到第\(i\)個數的時候,就可以通過二分法,找到小於\(a[i]\),且最後一個數最大的子序列,將其該數新增到子序列後面。
程式碼如下:
#include <iostream>
#include <algorithm>
using namespace std;
const int N = 1010;
int n;
int a[N];
int q[N];
int main()
{
cin >> n;
for(int i = 0; i < n; i ++)cin >> a[i];
int len = 0;
q[0] = -2e9;
for(int i = 0; i < n; i ++)
{
int l = 0, r =len ;
while(l < r) // 二分法找出小於等於a[i]的最大的子序列。
{
int mid = l + r + 1 >> 1;
if(q[mid] < a[i]) l = mid;
else r = mid - 1;
}
len = max(len, r + 1); // 更新長度
q[r + 1] = a[i]; // 更新末尾值
}
cout << len << endl;
return 0;
}
注:程式碼中含有部分細節沒有詳細說明,供讀者思考