博客
关于我
最长单调递增子序列O(nlogn) O(n2)动态规划算法
阅读量:800 次
发布时间:2019-03-25

本文共 3190 字,大约阅读时间需要 10 分钟。

最长递增子序列(LIS)分析

1. 基于贪心算法的O(nlogn)方法

最长递增子序列(LIS)的问题可以通过贪心算法在O(nlogn)时间复杂度内解决。这个方法主要基于以下思路:

  • 队列构建:每个人都记住他在队列中的前一个人,前提是他比前一个人高。
  • 位置选择:当第i个人加入队列时,他会在现有的队列中找到一个位置j,使得j的位置上的某个人比他矮,但j+1位置上的某个人(如果有)比他高。
  • 替换原则:如果i比j+1位置的人高,他会替换j+1位置的人。
  • 追溯:最终,我们只需要找到最长队列的最后一个位置,并反向询问每个人的前一个人,直到找到队列的开头。
  • 例子说明

    以数组{1,3,5,4,4,8,6,7}为例:

    • a[0]=1:LIS长度为1,记录为last[1]=1。
    • a[1]=3 >1:LIS长度为2,last[2]=3。
    • a[2]=5 >3:LIS长度为3,last[3]=5。
    • a[3]=4:可以替换last[3]=5,长度还是3,last[3]=4。
    • a[4]=4:无变化。
    • a[5]=8 >5:LIS长度为4,last[4]=8。
    • a[6]=6:替换last[4]=8,last[4]=6。
    • a[7]=7 >6:LIS长度为5,last[5]=7。

    最终,LIS长度为5,last数组为{1,3,4,6,7}。

    认可队列

    通过上述方法,我们可以为每个人记录他的“认可者”(即他前面可以组成LIS的位置)。具体表格如下:

    认可者索引 a[i] LIS长度 认可最后一人 认可最后索引
    0 1 1 1 0
    1 3 2 3 1
    2 5 3 5 2
    3 4 3 4 3
    4 4 3 4 3
    5 8 4 8 5
    6 6 4 6 4
    7 7 5 7 6

    从表格中可以看出,最长队列由7认可的队列构成,队列为{1,3,4,6,7}。

    代码实现

    #include 
    using namespace std;int main() { int maxn, i; cin >> maxn; int a[maxn]; for (i = 0; i < maxn; ++i) { cin >> a[i]; } int last[maxn], last_id[maxn], pre[maxn]; int len = 0, maxi; // 初始化 fill(last.begin(), last.end(), -1); for (int i = 0; i < maxn; ++i) { auto pos = lower_bound(last, last + maxn, a[i]); if (pos != last + maxn) { int idx = pos - last; if (len > idx) { pre[i] = last_id[idx - 1]; } else { pre[i] = -1; } last[idx] = a[i]; last_id[idx] = i; if (idx > len) { len = idx; maxi = i; } } else { if (len == maxn) { // 不允许,比 Array 过长处理 } else { *pos = a[i]; last[pos - last] = a[i]; last_id[pos - last] = i; pre[i] = last_id[pos - last - 1]; } } } int lis[maxn]; for (int i = len - 1; i >= 0; --i) { lis[i] = a[maxi]; maxi = pre[maxi]; } cout << "最长公共子串长度:" << len << endl; for (int i = 0; i < maxn; ++i) { if (a[i] == lis[0]) { // 展示过程 break; } } return 0;}

    2. 动态规划算法

    动态规划方法基于以下思想:

    • 状态定义dp[i]记录以第i个元素结尾的最长递增子序列的长度。
    • 状态转移dp[i] = max(dp[j] + 1),其中j < i且a[j] < a[i]。
    • 优化:如果有多个满足条件的j,选择dp[j]最大的那个j。
    • 辅助数组lastidx记录每个ds[i]对应的j值,用于反向追溯最长子序列。

    代码示例

    #include 
    using namespace std;int main() { int a[6] = {1, 3, 5, 4, 4, 6}; int dp[6], lastidx[6]; for (int i = 0; i < 6; ++i) { dp[i] = 1; lastidx[i] = i; for (int j = 0; j < i; ++j) { if (a[j] < a[i] && dp[j] + 1 > dp[i]) { dp[i] = dp[j] + 1; lastidx[i] = j; } } } int maxx = 0, maxi = 0; for (int i = 0; i < 6; ++i) { if (dp[i] > maxx) { maxx = dp[i]; maxi = i; } } int lis[6]; for (int i = maxx - 1; i > 0; --i) { lis[i] = a[maxi]; maxi = lastidx[maxi]; } cout << "最长公共子串长度:" << maxx << endl; return 0;}

    3. 最长公共子序列法

    对于给定的数组A,通过以下步骤解决LIS问题:

  • 排序数组:将数组A排序得到数组B。
  • 求LCS:使用动态规划求解A和B的最长公共子序列(LCS)。
  • 去重:由于LCS可能包含重复值,将其去重得到最终的LIS。
  • 示例:假设A = {1, 3, 5, 4, 4, 6},B = {1, 3, 4, 4, 5, 6},LCS为{1, 3, 4, 4, 6},去重后得到{1, 3, 4, 6}。

    通过上述方法,我们可以在O(NlogN + N2)的时间复杂度内解决LIS问题,并通过动态规划或贪心算法实现优化。这些方法在多种场景下都有广泛的应用。

    转载地址:http://vzdyk.baihongyu.com/

    你可能感兴趣的文章
    selenium+python之切换窗口
    查看>>
    重载和重写的区别:
    查看>>
    搭建Vue项目步骤
    查看>>
    账号转账演示事务
    查看>>
    SpringBoot找不到@EnableRety注解
    查看>>
    在Vue中使用样式——使用内联样式
    查看>>
    Find Familiar Service Features in Lightning Experience
    查看>>
    Explore Optimization
    查看>>
    map[]和map.at()取值之间的区别
    查看>>
    【SQLI-Lab】靶场搭建
    查看>>
    【Bootstrap5】精细学习记录
    查看>>
    Struts2-从值栈获取list集合数据(三种方式)
    查看>>
    推荐几篇近期必看的视觉综述,含GAN、Transformer、人脸超分辨、遥感等
    查看>>
    VTK:可视化之RandomProbe
    查看>>
    block多队列分析 - 2. block多队列的初始化
    查看>>
    Java时间
    查看>>
    不编译只打包system或者vendor image命令
    查看>>
    【编程】C语言入门:1到 100 的所有整数中出现多少个数字9
    查看>>
    flink启动(二)
    查看>>
    pair的用法
    查看>>