最长上升子序列 (Longest Increasing Subsequence, LIS) 是一类典型的动态规划 (Dynamic Programming) 问题
对于一个序列 $b$ 来说,当 $b_1 < b_2 < \cdots < b_n$ 的时候,我们称这个序列为严格上升的序列。最长上升子序列,顾名思义,也就是在给定序列中最长的严格上升的子序列(不一定连续)
动态规划
考虑维护一个数组 dp[]
,dp[i]
表示以第 $i$ 个数结尾的最长上升子序列的长度,对于长度为 $i$ 的序列 arr[]
来说,我们只要用 arr[i]
和序列 $i- 1$ 中的每一位进行比较,对于每一位小于 arr[i]
的数,都是有可能更新最长上升子序列长度的数,我们取其中最大一个,加上 arr[i]
本身($dp[i] = max{dp[j]} + 1$),便是以第 $i$ 个数结尾的最长上升子序列的长度
所以我们可以得出状态转移方程
$$ dp[i] = max{dp[j]} + 1, j < i \ \&\&\ arr[j] < arr[i] $$
代码实现
for(int i = 0; i < nums.size(); ++i)
{
dp[i] = 1;
for(int j = 0; j < i; ++j)
{
if(arr[j] < arr[i])
dp[i] = max(dp[i], dp[j] + 1);
}
ans = max(ans, dp[i]);
}
时间复杂度
显而易见,时间复杂度为 $O(n^2)$
二分 + 贪心
我们还是考虑维护一个数组 dp[]
及当前长度 $len$,dp[i]
表示目前计算的的上升子序列的第 $i$ 个元素的最小值
- 那么,每当我们拿到一个新元素
x
,我们和dp[len]
进行对比,如果dp[len] < x
,那么就可以直接将 x 接入到最长上升子序列中,形成一个更长的子序列,并且len++
- 如果
dp[len] >= x
,找到dp[]
中第一个大于等于a
的元素,用a
替换这个元素,这样能保证序列仍然是上升子序列,并且有更好的更新潜力(因为最后的元素变得更小了)
我们以序列 {1,4,5,3,4,5}
为例:
开始,我们将序列头 $1$ 添加进数组 dp[]
然后,我们将数组 dp[]
的末尾和 $4$ 进行判断,dp[]
数组末尾较小,dp[++len] = 4
同样,我们也将 $5$ 插入 dp[]
数组的尾部
当比较到 $3$ 时,我们发现,dp[]
数组的末尾 $> 3$,所以我们将 $3$ 插入到第一个大于它的位置,以期之后可以获得更小的末尾(这时序列的长度未变)
同样,因为末尾的 $5 > 4$,所以我们也将它插入到第一个大于它的位置
最后,我们将 $5$ 插入序列的末尾,得到了序列 {1,4,5,3,4,5}
的最长上升子序列 {1,3,4,5}
代码实现
int len = 1;
dp[1] = 1;
for(int i = 2; i <= n; ++i)
{
if(arr[i] > dp[len])
dp[++len] = arr[i];
else
*upper_bound(dp + 1, dp + n, arr[i]) = arr[i];
}
时间复杂度
每次循环中最慢时间为 $\log n$,故总时间复杂度为 $O(n\log n)$
树状数组 $O(n\log n)$
// To Do