这次我们来讲解一个叫做“最长非下降子序列”的问题及他的O(n^2)解法。

首先我们来描述一下什么是“最长非下降子序列”。

给你一个长度为n的数组a,在数组a中顺序找到最多的元素(这些元素的顺序不能乱,但是可以不连续),使得这些找出的元素最多,同时要保证找出的元素的数列中前面的元素要小于等于后面的元素,则这些元素组成的一个新的数组就是这个数组的最长非下降子序列。

符合这样的一个要求的问题就是“最长非下降子序列”问题。其中最重要的就是前一个元素的值要小于等于后一个元素的值。

例如,给定一个数组a,其元素排列如下:

1 3 2 4 5

1 3 4 5

1 2 4 5

都是其最长非下降子序列。

如果将条件“小于等于”稍加改变,就会衍生出另外的一些问题,如:

  • 小于:最长上升子序列
  • 大于:最长下降子序列
  • 大于等于:最长非下降子序列

不过我们可以很明显的看到,这些解决的都是同样的问题!所以我们在这个题目里面讲只重点讲解“最长非下降子序列”问题。

求最长非下降子序列有O(N*logN)的解法,不过我们在这篇随笔中先介绍O(n^2)的解法,以后有机会再介绍前面这种解法。

O(n^2)解法 —— 动态规划解法

我们可以发现,对于一个长度为n的数组a,我们同时开一个长度为n的数组f,其中f[i]表示以第i个元素结尾的最长上升子序列的长度,则状态转移方程可以表示为:

f[i]=max{f[j]+1},其中0<=j<=i-1,并且a[j]<=a[i]

求解数组a的最长上升子序列的长度的代码如下:

#include <iostream>
using namespace std;
int a[10010], f[10010], n, s;
int main()
{
cin >> n;
for (int i = 0; i < n; i ++)
cin >> a[i];
s = 0;
for (int i = 0; i < n; i ++)
{
f[i] = 1;
for (int j = 0; j < i; j ++)
{
if (a[j] <= a[i])
{
f[i] = max(f[i], f[j] + 1);
}
}
if (f[i] > f[s])
{
s = i;
}
}
cout << "max=" << f[s] << endl;
return 0;
}

注意:代码中的变量s用于保存最长连续子序列的最后一个数的坐标,则其对应的f[s]即为最长连续子序列的长度。

输出一个最长非下降子序列

有的时候题目不仅要求最长非降子序列的长度,还要求这个最长非降子序列(即完整地输出这个最长非下降子序列),这个时候应该怎么办呢?

我们可以再开一个长度为n的数组pre,pre[i]表示第i个元素所在的最长非下降子序列中他的前一个元素的坐标,那么,如果我们可以编写如下的一个递归函数来顺序输出一个最大非下降子序列:

{
if (i == -1)
return;
output(pre[i]);
cout << a[i] << " ";
}

同时,我们在原来的程序当做稍作修改,增加了pre函数的初始化和pre的计算部分,改进的代码如下:

#include <iostream>
using namespace std;
int a[10010], f[10010], pre[10010], n, s;
void output(int i)
{
if (i == -1)
return;
output(pre[i]);
cout << a[i] << " ";
}
int main()
{
cin >> n;
for (int i = 0; i < n; i ++)
cin >> a[i];
s = 0;
for (int i = 0; i < n; i ++)
pre[i] = -1;
for (int i = 0; i < n; i ++)
{
f[i] = 1;
for (int j = 0; j < i; j ++)
{
if (a[j] <= a[i])
{
if (f[j] + 1 > f[i])
{
f[i] = f[j] + 1;
pre[i] = j;
}
}
}
if (f[i] > f[s])
{
s = i;
}
}
cout << "max=" << f[s] << endl;
output(s);
return 0;
}

该函数可以输出数组a的一个最长非下降子序列。

输出字典序最小的最长非下降子序列

按照我们之前的例子:

1 3 2 4 5

他有两个最长非下降子序列1 3 4 51 2 4 5,如果我们运行了上面的程序,当我们输入:

5
1 3 2 4 5

的时候,出来的结果是

max=4
1 3 4 5

如果我们要求输出的最长非下降子序列是字典序最小的那个,则程序就出错了,原因在于我们没有对“字典序最小”这个条件进行处理。

那么怎么对“字典序最小”这个条件进行处理呢?我们就要稍微修改一下动态规划中求pre的状态转移方程了!

原来的状态转移方程式这样的:

            if (a[j] <= a[i])
{
if (f[j] + 1 > f[i])
{
f[i] = f[j] + 1;
pre[i] = j;
}
}

要考虑字典序最小,我们在判断的时候不光要判断f[j] + 1 > f[i]的情况,还要判断f[j] + 1 == f[i]的情况,当f[j] + 1 == f[i]时,我们要判断一下a[j]和a[pre[i]]哪个小,如果a[j]要小,则我们必须要果断地将pre[i]的值改为j。

该部分改进的代码如下:

                if (f[j] + 1 > f[i])
{
f[i] = f[j] + 1;
pre[i] = j;
}
if (f[j] + 1 == f[i])
{
if (pre[i] != -1 && a[j] < a[pre[i]])
pre[i] = j;
}

我们假设数组a的最长非下降子序列的长度为m,则通过上面的处理,我们已经保证了最长非下降子序列的前m-1个数是字典序最小的,那么怎么保证最后一个数是字典序最小的呢?

这个时候我们就要稍微处理一下s了(因为s是保存最后一个数的坐标的),我们原来的代码是这样的:

        if (f[i] > f[s])
{
s = i;
}

但是光考虑f[i] > f[s]的情况在这里是不够的,我们还要考虑一下f[i] == f[s]的情况,在这种情况下我们要比较一个a[i]和a[s]的大小,如果a[i]<a[s],则s应该被置为i,该部分改进的代码如下:

        if (f[i] > f[s])
{
s = i;
}
if (f[i] == f[s])
{
if (a[i] < a[s])
s = i;
}

所以,求一个数组a的字典序最小的最长非下降子序列的杨丽程序如下:

#include <iostream>
using namespace std;
int a[10010], f[10010], pre[10010], n, s;
void output(int i)
{
if (i == -1)
return;
output(pre[i]);
cout << a[i] << " ";
}
int main()
{
cin >> n;
for (int i = 0; i < n; i ++)
cin >> a[i];
s = 0;
for (int i = 0; i < n; i ++)
pre[i] = -1;
for (int i = 0; i < n; i ++)
{
f[i] = 1;
for (int j = 0; j < i; j ++)
{
if (a[j] <= a[i])
{
if (f[j] + 1 > f[i])
{
f[i] = f[j] + 1;
pre[i] = j;
}
if (f[j] + 1 == f[i])
{
if (pre[i] != -1 && a[j] < a[pre[i]])
pre[i] = j;
}
}
}
if (f[i] > f[s])
{
s = i;
}
if (f[i] == f[s])
{
if (a[i] < a[s])
s = i;
}
}
cout << "max=" << f[s] << endl;
output(s);
return 0;
}

最长非降子序列的O(n^2)解法的更多相关文章

  1. 最长非降子序列的N*logN解法

    之前讲到过求最长非降子序列的O(N^2)解法. 链接 这次在原来的基础上介绍一下N*logN解法. 该解法主要是维护一个数组minValue,minValue[i]表示最长上身子序列长度为i的数的最小 ...

  2. Codeforces Round #198 (Div. 2) D. Bubble Sort Graph (转化为最长非降子序列)

    D. Bubble Sort Graph time limit per test 1 second memory limit per test 256 megabytes input standard ...

  3. DP:凑零钱问题/最长非降子序列(C++)

    你给出一定数额的钱 i 元给我,我利用手中的硬币(m元, j元, k元...)兑换等值的钱给你,要求硬币数最少. 举例:给出1-11的钱,手中硬币有1元,3元,5元. 重点是找到状态和状态转移方程. ...

  4. 最长非降/下降子序列问题(DP)(待续...)

    注意:抽象成以下描述即为最长非降/下降子序列问题(一维状态) 问题描述:在一个无序的序列a1,a2,a3,a4…an里,找到一个最长的序列满足:(不要求连续) ai<=aj<=ak…< ...

  5. 求最长非降(递增)子序列LIS的长度,及注意事项

    非降序列(Increasing Sequence)例如: (1) 完全递增型序列:S={1,3,6,7,9} (2) 部分存在等于的序列:S={1,3,3,6,9} S的非降子序列:由原序列S的元素组 ...

  6. HDU 6357.Hills And Valleys-动态规划(区间翻转l,r找最长非递减子序列)

    题意:给一串由n个数字组成的字符串,选择其中一个区间进行翻转,要求翻转后该字符串的最长非降子序列长度最长,输出这个最长非降子序列的长度以及翻转的区间的左右端点 #include<bits/std ...

  7. HDU 1025-Constructing Roads In JGShining's Kingdom(最长不降子序列,线段树优化)

    分析: 最长不降子序列,n很大o(n^2)肯定超,想到了小明序列那个题用线段树维护前面的最大值即可 该题也可用二分搜索来做. 注意问题输出时的坑,路复数后加s #include <map> ...

  8. 题目:[NOIP1999]拦截导弹(最长非递增子序列DP) O(n^2)和O(n*log(n))的两种做法

    题目:[NOIP1999]拦截导弹 问题编号:217 题目描述 某国为了防御敌国的导弹袭击,发展出一种导弹拦截系统.但是这种导弹拦截系统有一个缺陷:虽然它的第一发炮弹能够到达任意的高度,但是以后每一发 ...

  9. [LeetCode] Longest Uncommon Subsequence I 最长非共同子序列之一

    Given a group of two strings, you need to find the longest uncommon subsequence of this group of two ...

随机推荐

  1. DES加密例子

    Java密码学结构设计遵循两个原则: 1) 算法的独立性和可靠性. 2) 实现的独立性和相互作用性. 算法的独立性是通过定义密码服务类来获得.用户只需了解密码算法的概念,而不用去关心如何实现这些概念. ...

  2. 多线程编程-- part 3 多线程同步->synchronized关键字

    多线程同时访问一个资源,可以会产生不可预料的结果,所以为这个资源加锁,访问资源的第一个线程为其加锁后,其他线程便不能在使用那个资源,直到锁被解除. 举个例子: 存款1000元,能取出800的时候我就取 ...

  3. 刨根究底字符编码之五——简体汉字编码方案(GB2312、GBK、GB18030、GB13000)以及全角、半角、CJK

    简体汉字编码方案(GB2312.GBK.GB18030.GB13000)以及全角.半角.CJK   一.概述 1. 英文字母再加一些其他标点字符之类的也不会超过256个,用一个字节来表示一个字符就足够 ...

  4. 元类(meta class)

    元类(meta class),这个名字想必很多人都听过,网上也有很多关于元类的介绍,今天我就按照自己这两天的理解来简单探讨一下这个玩意,有误之处还望指出. 首先,下载objc源码,源码地址:https ...

  5. MySQL开放远程登录

    在服务器上部署MYSQL每次观看MYSQL记录或者修改的时候都需要登录服务器,又烦又占资源.所以使用另一种方法:对外开放接口. 注:如果某些服务器开启防火墙屏蔽了某些接口就有可能导致远程用户无法登录M ...

  6. JDBC连接数据库程序

    废话少说,看了尚学堂的视频以及某大神的博客,总结出以下.(本文以oracle数据库为例) 创建一个JDBC连接数据库的程序,需要着手做以下几件事情: 注意,这里边使用了java.sql.Stateme ...

  7. go服务端----使用dotweb框架搭建简易服务

    使用dotweb框架搭建简易服务 go语言web框架挺多的,所谓琳琅满目,里面也有很多优秀的,比如echo.beego等,但体验下来,总是觉得哪里有点小疙瘩,后来才明白过来,echo太简单,很多日常使 ...

  8. DOM知识梳理

    DOM 我们知道,JavaScript是由ECMAScript + DOM + BOM组成的.ECMAScript是JS中的一些语法,而BOM主要是浏览器对象(window)对象的一些相关知识的集合. ...

  9. htm语言的语法基础及规则

    HTML的主要语法是元素和标签.元素是符合DTD(文档类型定义)的文档组成部分,如title(文档标题).IMG(图象).table(表格)等等.元素名不区分大小写的.HTML用标签来规定元素的属性和 ...

  10. twemproxy发送流程探索——剖析twemproxy代码正编

    本文想要完成对twemproxy发送流程--msg_send的探索,对于twemproxy发送流程的数据结构已经在<twemproxy接收流程探索--剖析twemproxy代码正编>介绍过 ...