方法一:
dp動態規劃
狀態設計:dp[i]代表以a[i]結尾的LIS的長度
狀態轉移:dp[i]=max(dp[i], dp[j]+1) (0<=j< i, a[j]< a[i])
時間複雜度:O(N^2)
例題:https://blog.csdn.net/y201619819/article/details/78354348
方法二:貪心+二分
時間複雜度Nlog(N)
a[i]表示第i個數據。
dp[i]表示表示長度爲i+1的LIS結尾元素的最小值。
利用貪心的思想,對於一個上升子序列,顯然當前最後一個元素越小,越有利於添加新的元素,這樣LIS長度自然更長。
因此,我們只需要維護dp數組,其表示的就是長度爲i+1的LIS結尾元素的最小值,保證每一位都是最小值,這樣子dp數組的長度就是LIS的長度。
dp數組具體維護過程同樣舉例講解更爲清晰。
同樣對於序列 a(1, 7, 3, 5, 9, 4, 8),dp的變化過程如下:
dp[0] = a[0] = 1,長度爲1的LIS結尾元素的最小值自然沒得挑,就是第一個數。 (dp = {1})
對於a[1]=7,a[1]>dp[0],因此直接添加到dp尾,dp[1]=a[1]。(dp = {1, 7})
對於a[2]=3,dp[0]< a[2]< dp[1],因此a[2]替換dp[1],令dp[1]=a[2],因爲長度爲2的LIS,結尾元素自然是3好過於7,因爲越小這樣有利於後續添加新元素。 (dp = {1, 3})
對於a[3]=5,a[3]>dp[1],因此直接添加到dp尾,dp[2]=a[3]。 (dp = {1, 3, 5})
對於a[4]=9,a[4]>dp[2],因此同樣直接添加到dp尾,dp[3]=a[9]。 (dp = {1, 3, 5, 9})
對於a[5]=4,dp[1]< a[5]< dp[2],因此a[5]替換值爲5的dp[2],因此長度爲3的LIS,結尾元素爲4會比5好,越小越好嘛。(dp = {1, 3, 4, 9})
對於a[6]=8,dp[2]< a[6]< dp[3],同理a[6]替換值爲9的dp[3],道理你懂。 (dp = {1, 3, 5, 8})
ok,這樣子dp數組就維護完畢,所求LIS長度就是dp數組長度4。
通過上述求解,可以發現dp數組是單調遞增的,因此對於每一個a[i],先判斷是否可以直接插入到dp數組尾部,即比較其與dp數組的最大值即最後一位;如果不可以,則找出dp中第一個大於等於a[i]的位置,用a[i]替換之。
這個過程可以利用二分查找,因此查找時間複雜度爲O(logN),所以總的時間複雜度爲O(NlogN)
例題:
LIS 是最長上升子序列。什麼是最長上升子序列? 就是給你一個序列,請你在其中求出一段最長嚴格上升的部分,它不一定要連續。
就像這樣:2, 3, 4, 7 和 2, 3, 4, 6 就是序列 2 5 3 4 1 7 6 的兩個上升子序列,最長的長度是 4。
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5 + 9;
int f[N], a[N];
int n;
int find(int l, int r, int x) {//二分查找要插入的位置
while (l < r) {
int mid = (l + r) / 2;
if (f[mid] < x) {
l = mid + 1;
}
else {
r = mid;
}
}
return l;
}
int lis() {
int len = 0;
for (int i = 0; i < n; i++) {
int k = find(0, len, a[i]);
f[k] = a[i];
if (k == len) {
len++;
}
}
return len;
}
int main() {
scanf("%d", &n);
for (int i = 0; i < n; i++) {
scanf("%d", a + i);
}
printf("%d\n", lis());
return 0;
}