最长递增子序列(Longest Increasing Subsequence)

14 篇文章 2 订阅
4 篇文章 1 订阅

定义

最长上升子序列(Longest Increasing Subsequence,LIS),在计算机科学上是指一个序列中最长的单调递增的子序列。

问题描述

给定一个长度为 N 的数组,找出一个最长的单调自增子序列(不一定连续,但是顺序不能乱)。例如:给定一个长度为 5 的数组{5, 6, 1, 2, 8},则其最长的单调递增子序列为 {5,6,8},长度为 3。

解法

动态规划

时间复杂度

该方法的时间复杂度为 O(n^{2})

实现过程

下面我们用一个实例来分析一下动态规划求解 LIS 的整个过程。假设数组 A 的内容为 {5, 6, 1, 2, 8}。

1、第一个元素直接设置 LIS 长度为 1 即可。如下图所示。

2、第二个元素 6 大于前面所有元素进行比较。5<6,则 LIS[1] = LIS[0]+1 = 2。如下图所示。

3、第三个元素 1 和前面的所有元素进行比较。1<6,则 LIS 的长度可能为 1;1<5,则 LIS 的长度可能为 1;取最大值,LIS[2]=1 。如下图所示。

4、第四个元素 2 和前面的所有元素进行比较。1<2,则 LIS 的长度可能为 LIS[2]+1 = 2;6>2,则 LIS 的长度可能为 1;5>2,则 LIS 的长度可能为 1;取最大值,LIS[3]=2 。如下图所示。

5、第五个元素 8 和前面的所有元素进行比较。2<8,则 LIS 的长度可能为 LIS[3]+1 = 3;8>1,则 LIS 的长度可能为 LIS[2]+1 = 2;8>6,则 LIS 的长度可能为 LIS[1]+1 = 3;8>5,则 LIS 的长度可能为 LIS[0]+1 = 2;取最大值,LIS[4]=3 。如下图所示。

算法思路

设长度为 N 的数组为 {a0,a1, a2, ..., an-1),则假定以 aj 结尾的数组序列的最长递增子序列长度为 LIS(j),则 LIS(j) = {max(LIS(i))+1, i<j 且 a[i] < a[j] }。

二分查找

时间复杂度

该方法的时间复杂度为 O(n*logn)

算法描述

我们可以引入一个新数组 maxV,该数组的特性为:

长度为 1 的递增子序列最大元素的最小值为 maxV[1];

长度为 2 的递增子序列最大元素的最小值为 maxV[2];

长度为 LIS[i] 的递增子序列最大元素的最小值为 maxV[LIS[i]]。

首先,证明 maxV[] 是递增的,因此可以使用二分搜索。我们可以用数学归纳法即可证明:若前 k 个元素是递增的,一定有 maxV[k+1] \geq maxV[k]

证明:假设不成立,则 maxV[k+1] < maxV[k]。

根据 maxV[k+1] 的定义,存在一个长度是 k+1 的 LIS,并且以 maxV[k+1] 为最大元素。将上述子序列去掉最后一个元素maxV[k+1], 得到长度为 k,且最大元素 < maxV[k+1] < maxV[k]。

这显然与 maxV[k] 的定义矛盾。所以假设不成立。

实现过程

我们用一个实例来分析一下二分查找求解 LIS 的整个过程。假设数组 A 的内容为 {5, 6, 1, 2, 8}。

我们用 LIS[i-1] 表示长度为 i 的最长递增子序列末尾的数据。

1、第一个元素直接加入到 LIS 数组中。LIS[0]=5,表示长度为 1 的 LIS 数组最后一个元素是 5。如下图所示。

2、第二个元素为 6,因为 6>LIS[0],构成递增,将数字 6 加入到 LIS 数组中,即 LIS[1]=6,表示长度为 2 的 LIS 数组的末尾是 6。如下图所示。

3、第三个元素为 1,1<LIS[2],因此前面一定有一个位置的数据可以换成 1,并且后面的递增性质不会被破坏。因此我们使用二分查找在 LIS 数组中找到 1 的位置,我们知道 lower_bound 查找的位置为 0。也就是说 LIS[0] 可以被替换为 1。如下图所示。

4、第四个元素为 2,2<LIS[2],因此前面一定有一个位置的数据可以换成 2,并且后面的递增性质不会被破坏。因此我们使用二分查找在 LIS 数组中找到 2 的位置,我们知道 lower_bound 查找的位置为 1。也就是说 LIS[1] 可以被替换为 2。如下图所示。

4、第五个元素为 8,8>LIS[2],构成递增,将数字 8 加入到 LIS 数组中,即 LIS[2]=8,表示长度为 3 的 LIS 数组的末尾是 8。如下图所示。

这样,我们完成了遍历,这时候我们可以发现 LIS 数组的小标为 2,表示我们要求解的 LIS 长度为 3。

算法思路

将 array[i] 在当前的 maxV[] 数组中进行二分搜索,找到位置 k,maxV[k] < array[i] < maxV[k+1]。

将 array[i] 加入 maxV[] 数组。仅仅影响 maxV[k+1]  (maxV[k+1] = array[i]),而对其他的元素不产生影响。

参考实现

int LIS(int *a, int n) {
    if (n<=0) {
        return 0;
    }

    vector<int> maxV;
    maxV.push_back(a[0]);
    for(int i=1; i<n; ++i) {
        if (a[i] > *maxV.rbegin()) {
            maxV.push_back(a[i]);
        } else {
            *lower_bound(maxV.begin(), maxV.end(), a[i]) = a[i];
        } 
    }
    return maxV.size();
}

 

  • 5
    点赞
  • 33
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

努力的老周

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值