博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
最长上升子序列
阅读量:5024 次
发布时间:2019-06-12

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

问题描述 : 在一个给定的无序序列当中找出最长且递增的子序列 (不一定连续)

 

对于这个经典问题通常有两种时间复杂度不一样方法来解决一个是O(n2)的算法

另外一个是采用了二分或树状数组O(nlogn)的算法。

 

动态规划 O(n2) 算法 : 

对于序列 squ[1]、squ[2]……squ[n] 分别考虑以每一个元素作为子序列的最后一个元素所能得出的最长长度,使用 dp[i] 来记录这个值 即 dp[i] 表示以第 i 个元素作为子序列的最后一个元素所能组成的最长的递增子序列的最长长度。则初始条件为 dp[1~n] = 1 而后状态转移方程则是 dp[i] = max{ dp[i], dp[j]+1 } 其中 j  的范围为 1~(i-1) 且满足 squ[i] > squ[j]

#include
using namespace std;const int maxn = 3e3 + 10;int n, len, idx, squ[maxn], dp[maxn], pre[maxn];inline void LIS(){ len = idx = 1;///保存最长长度 以及 最长长度结尾元素的下标 dp[1] = 1; for(int i=2; i<=n; i++){ dp[i] = 1;///初始条件 for(int j=1; j
squ[i] ///如果要不严格上升序列 改成 squ[j] <= squ[i] if(squ[j] < squ[i] && dp[j]+1 > dp[i]){
dp[i] = dp[j]+1; pre[i] = j;///pre数组记录每一个 i 的前驱元素,方便记录序列 } } if(dp[i] > len){ len = dp[i]; idx = i; } }}int main(void){ scanf("%d", &n); for(int i=1; i<=n; i++){ scanf("%d", &squ[i]); pre[i] = i; } LIS(); printf("%d\n", len); stack
res; while(pre[idx] != idx){
///根据子序列最后一个元素下标和pre数组找出前面的节点,用栈存储并输出 res.push(squ[idx]); idx = pre[idx]; } printf("%d ", squ[idx]); while(!res.empty()){ printf("%d ", res.top()); res.pop(); }puts(""); return 0;}/*Input51 2 6 2 4Output31 2 6*/
View Code

 

二分 O(nlogn) 算法 :  ()

对于序列 squ[1]、squ[2]……squ[n],使用一个 dp[i]  来表示长度为 i  的序列的最后一个元素,注意这里和上面不一样,上面的 dp[i] 值是长度,而这里是具体的元素值是多少,长度则是 i 。那如何做到O(nlogn)呢?下面先来说说做法 : 

① 使用 len 来保存当前的最长长度,如果当前元素 squ[i]  > dp[len] 也就是比最长长度的最后一个元素还要大则 dp[++len] = squ[i]

② 如果 squ[i] < dp[len] 那么在 dp[1 ~ len-1] 中找到刚好比 squ[i] 大的值将其用 squ[i] 来替换,即 idx = lower_bound(dp+1, dp+1+len, squ[i]) - dp 然后 dp[idx] = squ[i]

第一步好理解,那第二步这种做法的原因是什么呢?

我们先来考虑两个数 x、y 且 x < y 如果当前在 dp[1~(len-1)] 中的任意一个 dp[] 的值是 y ,那么现在进来一个 x 如果使用 x 去代替 y 不会对于当前的最长长度 len 有任何影响,但是会使得当前的序列更有"潜力",例如如果下一个数是 z 且 x < z < y 那么接下来二分找到的便是 x 前面的位置考虑是否被替换成 z ,如果现在进来的是很多个类似于 z 这样的数,那么整个序列每次二分找到的位置都会靠前,在不断替换的情况下就会把之前的 dp[len] 变成更小的值, dp[len] 变得更小,那么 len++ 的可能性就更大,所以将 x 去替换 y 更优。

 

以下是代码

值得一提,二分的方法在恢复具体的子序列这一方面貌似是有BUG的

所以需要具体的序列建议采用 DP 的写法

#include
using namespace std;const int maxn = 1e5 + 10;const int INF = 0x3f3f3f3f;int dp[maxn], arr[maxn], N;int strictly_increase() ///求严格上升的子序列、已验证 洛谷OJ ==> P1020{ int Len = 0; memset(dp, 0, sizeof(dp)); dp[Len] = -INF; for(int i=0; i
P1020{ int Len = 0; memset(dp, INF, sizeof(dp)); dp[Len] = -INF; for(int i=0; i
View Code

 

瞎想 : 实际上我感觉 O(nlogn) 的算法不像 dp ,更让我感觉是个贪心构造……

 

树状数组 O(nlogn) 算法 ()

使用树状数组来求解 LIS 问题

首先需要说一个前提、树状数组是可以维护前缀的最值的、只要把代码的累加变成维护最值即可

以下的用树状数组解决 LIS 的具体做法

定义树状数组 c[i] 表示以第 i 个数为结尾的 LIS 长度

那么对于第 i 个数的 c[i] 可以从前缀的所有数转移而来 即 c[1~(i-1)]

为了方便转移、我们记录原序列中所有数的位置、然后排序、最后升序去考虑每一个数

文字描述有点难、以下直接给出核心代码去理解

 

int ans = 0;for(int i=1; i<=N; i++){    int Len = query(num[i].id);///查询在原序列第 num[i].id 数前面的数为结尾的 LIS 长度    add(num[i].id, ++Len);///给当前 num[i].id 为结尾的数的 LIS 长度更新为 ++Len    ans = max(ans, Len);///维护最优答案}

 

以下是完整代码

 

#include
#define lowbit(i) (i&(-i))using namespace std;const int maxn = 3e3 + 10;struct NUM{ int id, val; bool operator < (const NUM &rhs) const{ if(this->val == rhs.val) return this->id < rhs.id; else return this->val < rhs.val; };}num[maxn];int c[maxn];int N, mx;inline void add(int i, int val){ while(i <= mx){ c[i] = max(val, c[i]); i += lowbit(i); }}int query(int i){ int ret = 0; while(i > 0){ ret = max(ret, c[i]); i -= lowbit(i); } return ret;}int main(void){ mx = -1; scanf("%d", &N); for(int i=1; i<=N; i++) scanf("%d", &num[i].val), num[i].id = i, mx = max(mx, num[i].val); sort(num+1, num+1+N); int ans = 0; for(int i=1; i<=N; i++){ int Len = query(num[i].id);///查询在原序列第 num[i].id 数前面的数为结尾的 LIS 长度 add(num[i].id, ++Len);///给当前 num[i].id 为结尾的数的 LIS 长度更新为 ++Len ans = max(ans, Len);///维护最优答案 } printf("%d\n", ans); return 0;}
View Code

 

 

 

一些题目

分析 :有一个结论,求最长不严格下降子序列的个数等于求最长上升子序列的个数,后面的就是裸题了

#include
using namespace std;const int maxn = 1e6 + 10;const int INF = 0x3f3f3f3f;int arr[maxn];int dp[maxn];int N;int Not_increase(){ int Len = 0; memset(dp, INF, sizeof(dp)); dp[Len] = -INF; for(int i=0; i
View Code

 

 

转载于:https://www.cnblogs.com/LiHior/p/7515944.html

你可能感兴趣的文章
numpy.squeeze()的用法
查看>>
数字滤波器 C语言
查看>>
JAVA基础知识 String s = new String("ABC") VS String s = "abc"
查看>>
mysql 数据库,表存储 大小
查看>>
将博客搬至CSDN
查看>>
Spring AOP编程
查看>>
2017.2.18[codevs3311][bzoj3668]NOI2014D1T1起床困难综合症
查看>>
MySQL表的四种分区类型
查看>>
最全的分区类型及详解
查看>>
Python 类中__init__()方法中的形参与如何修改类中属性的值
查看>>
9.1.3 前端 - HTML body标签 - 文本样式
查看>>
ACID属性
查看>>
cnpm不是内部命令的解决方案:配置环境变量
查看>>
7系列FPGA远程更新方案-QuickBoot(转)
查看>>
导出帐号和权限脚本
查看>>
markdown公式编辑参考
查看>>
利用运行时给模型赋值
查看>>
归并排序求逆序对
查看>>
SQL2008用sql语句给字段添加说明
查看>>
JavaScript的对象创建
查看>>