热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

优化最长上升子序列_动态规划优化最长上升子序列详解|学步园

最近在学习动态规划的对于研究同样问题的朋友有所帮助。首先,该问题可描述为:给出一个由n个数组成的序列seq[1..n],找出它的最长非递减

最近在学习动态规划的对于研究同样问题的朋友有所帮助。

首先,该问题可描述为:给出一个由n个数组成的序列seq[1..n],找出它的最长非递减非连续序列。即求最大的 m 和a1,a2……,am,使得a1

希望新手能先将上面的题目描述读上两三遍,然后再往下看,以免因对题意模糊不清影响而理解。

下面我就来讲两种用动态规划思想来解此题的方法,其中第一种是原始的方法,虽然比较容易理解,但是算法效率不高,时间复杂度为O(n^2),而第二种算法是在第一种算法基础上进行优化得到的,其时间复杂度为O(n log n),虽然算法效率得到了提高,但是算法的理解难度也变大了。

先从简单的算法入手&#xff0c;首先我们用seq[1....n]表示要找最长子序列的序列&#xff0c;sub_seq[]就代表要找的最长子序列。和一般的用动态规划思想解决的问题一样&#xff0c;我们可以这样来分析问题&#xff0c;如果以seq[k]为尾元素的序列seq[1....k](1<&#61;k

基本思想明白了之后&#xff0c;就开始给该问题定义状态&#xff0c;我们定义一个数组dp[1....n]来表示状态&#xff0c;dp[i]的含义是序列seq[1.....i]的最长子序列的长度&#xff0c;好了&#xff0c;那么dp[k&#43;1]可以这样来求&#xff0c;对seq[1...k]从前往后扫描&#xff0c;如果seq[i](1<&#61; i <&#61; k)小于等于seq[k&#43;1],那么就比较dp[i]&#43;1和dp[k&#43;1](dp[k&#43;1]初始化为一个很小的数)的大小&#xff0c;如果dp[i] &#43; 1大的话&#xff0c;就把dp[i] &#43;1赋给dp[k&#43;1]&#xff0c;因为seq[1....i]的最长子串的长度为dp[i]&#xff0c;和seq[k&#43;1]组合后可形成长度为dp[i]&#43;1的子串。最后&#xff0c;dp[1.....n]都求出来以后&#xff0c;从dp[]中选出最大的值&#xff0c;就是序列seq[1...n]的最长子序列。如果不止要求长度&#xff0c;还要得到具体的子序列的话&#xff0c;可以用一个数组pre[]来保存上一个结点&#xff0c;dp[i]的含义就是seq[i]的上一个元素&#xff0c;这个不是这里讲的重点&#xff0c;就不多说了。下面给出的是代码。实现的时候用了很多STL中的东西&#xff0c;但是如果读者不了解STL的话&#xff0c;用基本的数组也可以实现。代码中的函数LNDSS就是用来求最长序列的。

#include

#include

#include

#include

#include

#include

using namespace std;

/*

同态规划法求最长非递减非连续子序列

状态转移方程&#xff1a;dp[i] &#61; 1 &#43; max{ dp[k] | k

时间复杂度&#xff1a;O(n lgn)

*/

void LNDSS(vector& seq, vector& sub_seq)

{

vector dp(seq.size(), 0);//dp[i]的值代表以seq数组中的第i个元素为尾的非连续序列的 最大 长度

vector pre(seq.size(), -1);//pre[i]的值代表在非连续序列中第i个元素的前一个元素的下标

int i,j;

for(i &#61; 0; i

{

for(j &#61; 0; j

{

if(seq[i] >&#61; seq[j] && dp[j] &#43; 1 > dp[i])

{

dp[i] &#61; dp[j] &#43; 1;

pre[i] &#61; j;

}

}

}

int tmp &#61; max_element(dp.begin(), dp.end()) - dp.begin();

do

{

sub_seq.push_back(seq[tmp]);

tmp &#61; pre[tmp];

}while(-1 !&#61; tmp);

reverse(sub_seq.begin(), sub_seq.end());

}

int main(void)

{

vector seq;

int tmp;

while(scanf("%d",&tmp), tmp)

{

seq.push_back(tmp);

}

vector sub_seq;

LNDSS(seq, sub_seq);

for(int i &#61; 0; i

{

printf("%d\n", sub_seq[i]);

}

printf("\n");

return 0;

}

好的&#xff0c;下面来讲第二种比较复杂的方法。这种方法的是深入分析了问题后得来的。再来回顾上一种方法&#xff0c;其实我们可以发现&#xff0c;上面在求每个状态dp[i]的时候&#xff0c;都必须遍dp[1...i-1]这i-1种状态&#xff0c;在动态规划的过程中要求n个状态&#xff0c;每个状态又要做i-1个动态转移操作&#xff0c;所以总共要做的基本操作个数是1&#43;2&#43;3&#43;....&#43; (i-1) &#43; (n -1),是一个等差级数&#xff0c;所以上面的算法时间规模是O(n^2)。那么如果要对算法进行优化的话&#xff0c;需要那个地方优化呢&#xff1f;我们可以这样分析&#xff0c;首先&#xff0c;求n个状态这一步很难再优化了&#xff0c;但是我们仍然可能在求状态的时候减少决策所产生的操作。第一种算法求每个状态&#xff0c;比如求dp[n]的时候要做n-1个状态转移操作&#xff0c;那么是不是可以只进行一次状态转移呢&#xff1f;其实第二种算法的优化就是这样的。

在讲原理之前先把算法的大致操作给大家描述一下。你在求状态值dp[i]的时候&#xff0c;要是按照第一种方法&#xff0c;需要参考前面i-1个元素的状态值&#xff0c;但是其实前面的这i-1种状态值并不是都需要参考&#xff0c;有一些注定了对dp[i]这个状态值没有影响(至于为什么没有影响&#xff0c;下面会讨论)。第二种算法是这样的&#xff0c;给你一个类似于集合的容器set(可以用数组、栈、平衡树等实现)&#xff0c;假如你在求状态值dp[i]的时候&#xff0c;这个容器里面存的东西就是你做状态转移时所需要参考的前面的状态&#xff0c;注意里面的状态个数要小于或者等于i-1。所以你要求的dp[i]取决于这个容器里面状态。这个容器里面的东西你可以先认为它们是一个个的结构体变量&#xff0c;结构体的里面的三个成员为①、seq[1...n]中某个元素的下标&#xff0c;不妨设为i&#xff0c;②序列seq[1...n]中第i个元素的值seq[i]。③序列seq[1...n]中第i个元素所对应的状态值dp[i]。请读者注意一下&#xff0c;并不是seq[1...n]中每个元素对应的状态值dp[i]都在set中。set中存放的元素值才是我们求状态值dp[i]的时候做决策需要参考的&#xff0c;一些不需要参考的前面已经求出的状态值不会出现在集合中(至于哪个dp[i]才是我们做决策时用到的&#xff0c;在下面会讲到)&#xff0c;而且我们每求一个状态值dp[i]&#xff0c;只需要在集合set中做复杂度为O(logn)的操作(至于怎么操作&#xff0c;下面讲)&#xff0c;这样的话&#xff0c;算法的总体规模降为O(nlogn)。

在算法的大体运行机制了解之后&#xff0c;我们分析算法的原理。来看这个特殊的集合set&#xff0c;在集合中任取两个元素&#xff0c;如第i个和第j个&#xff0c;(注意i、j的大小关系不定&#xff0c;任取)可知&#xff1a;如果seq[i] &#61; dp[j]的情况不会出现&#xff0c;即dp[i] dp[j] 那么在求dp[k]的时候&#xff0c;k > i > j,则dp[k]既可以从dp[i]转移&#xff0c;也可以选择从dp[j]转移&#xff0c;但是从dp[i]转移得到的dp[k]

&#61; dp[i] &#43; 1,而从dp[j]转移得到的dp[k] &#61; dp[j] &#43; 1,但是dp[i] > dp[j]&#xff0c;所以从dp[i]转移就可以&#xff0c;dp[j]根本没用处。②如果dp[i] &#61; dp[j] &#xff0c;那么dp[k]从dp[i] 转移和从dp[j]转移是一样的&#xff0c;所以dp[j]没有必要再set中。其实通过上面的分析&#xff0c;我们能够知道set里面的元素如果以seq[i]为主键按升序排好序的话&#xff0c;dp[i]也是升序的&#xff0c;这样就为我们做状态转移提供了一种大大提高效率的可能。

接下来就是状态转移了&#xff0c;任意求一个状态值dp[k],在求dp[k]之前dp[1]、dp[2]、.....‘dp[k-1]都已经求好了&#xff0c;并且对dp[k]有影响的前面的状态都放到了集合set中。我们要求dp[k],其实就是求得如何和前面已经得到的字串进行结合得到最长的字串&#xff0c;首先前面得到的子串的最后一个元素必须小于等于seq[k],因为只有这样才可以和seq[k]组成一个以seq[k]为最后一个元素的非递减非连续子序列。而set里面的值是按dp值(也是按seq值)递增排列的&#xff0c;我们要找的最适合转移的状态就是set中最后一个不小于seq[k]的值(不妨设为q)&#xff0c;所以dp[k]

&#61; dp[q] &#43; 1;

好了&#xff0c;下面就给大家奉上一段写的非常简练的代码&#xff0c;大家好好琢磨琢磨吧&#xff01;&#xff01;&#xff01;因本人知识水平有限&#xff0c;所言难免有许多不正确的地方&#xff0c;还有很多没有说明白的地方&#xff0c;欢迎大家批评指正。如果有联系的必要的话可以加我的QQ: 774267423

int LNDSS(int a[], int n)

{

int i, j, * b &#61; new int[n &#43; 1], ans &#61; 0;

b[ans] &#61; - 0x3f3f3f3f;

for(i &#61; 0; i

{

j &#61; upper_bound(b, b &#43; ans &#43; 1, a[i]) - b;

if(j > ans)

b[&#43;&#43;ans] &#61; a[i];

else if(a[i]

b[j] &#61; a[i];

}

delete b;

return ans;

}

这是第二种算法的参考资料&#xff0c;(如果我的你看不懂&#xff0c;可以看看这个试试)毛子青论文<>,一个最长子序列的算法



推荐阅读
  • 动态规划算法的基本步骤及最长递增子序列问题详解
    本文详细介绍了动态规划算法的基本步骤,包括划分阶段、选择状态、决策和状态转移方程,并以最长递增子序列问题为例进行了详细解析。动态规划算法的有效性依赖于问题本身所具有的最优子结构性质和子问题重叠性质。通过将子问题的解保存在一个表中,在以后尽可能多地利用这些子问题的解,从而提高算法的效率。 ... [详细]
  • Java容器中的compareto方法排序原理解析
    本文从源码解析Java容器中的compareto方法的排序原理,讲解了在使用数组存储数据时的限制以及存储效率的问题。同时提到了Redis的五大数据结构和list、set等知识点,回忆了作者大学时代的Java学习经历。文章以作者做的思维导图作为目录,展示了整个讲解过程。 ... [详细]
  • 本文讨论了使用差分约束系统求解House Man跳跃问题的思路与方法。给定一组不同高度,要求从最低点跳跃到最高点,每次跳跃的距离不超过D,并且不能改变给定的顺序。通过建立差分约束系统,将问题转化为图的建立和查询距离的问题。文章详细介绍了建立约束条件的方法,并使用SPFA算法判环并输出结果。同时还讨论了建边方向和跳跃顺序的关系。 ... [详细]
  • 本文介绍了P1651题目的描述和要求,以及计算能搭建的塔的最大高度的方法。通过动态规划和状压技术,将问题转化为求解差值的问题,并定义了相应的状态。最终得出了计算最大高度的解法。 ... [详细]
  • 3.223.28周学习总结中的贪心作业收获及困惑
    本文是对3.223.28周学习总结中的贪心作业进行总结,作者在解题过程中参考了他人的代码,但前提是要先理解题目并有解题思路。作者分享了自己在贪心作业中的收获,同时提到了一道让他困惑的题目,即input details部分引发的疑惑。 ... [详细]
  • 本文介绍了最长上升子序列问题的一个变种解法,通过记录拐点的位置,将问题拆分为左右两个LIS问题。详细讲解了算法的实现过程,并给出了相应的代码。 ... [详细]
  • 集合的遍历方式及其局限性
    本文介绍了Java中集合的遍历方式,重点介绍了for-each语句的用法和优势。同时指出了for-each语句无法引用数组或集合的索引的局限性。通过示例代码展示了for-each语句的使用方法,并提供了改写为for语句版本的方法。 ... [详细]
  • 李逍遥寻找仙药的迷阵之旅
    本文讲述了少年李逍遥为了救治婶婶的病情,前往仙灵岛寻找仙药的故事。他需要穿越一个由M×N个方格组成的迷阵,有些方格内有怪物,有些方格是安全的。李逍遥需要避开有怪物的方格,并经过最少的方格,找到仙药。在寻找的过程中,他还会遇到神秘人物。本文提供了一个迷阵样例及李逍遥找到仙药的路线。 ... [详细]
  • 本文介绍了C++中省略号类型和参数个数不确定函数参数的使用方法,并提供了一个范例。通过宏定义的方式,可以方便地处理不定参数的情况。文章中给出了具体的代码实现,并对代码进行了解释和说明。这对于需要处理不定参数的情况的程序员来说,是一个很有用的参考资料。 ... [详细]
  • 本文主要解析了Open judge C16H问题中涉及到的Magical Balls的快速幂和逆元算法,并给出了问题的解析和解决方法。详细介绍了问题的背景和规则,并给出了相应的算法解析和实现步骤。通过本文的解析,读者可以更好地理解和解决Open judge C16H问题中的Magical Balls部分。 ... [详细]
  • c语言\n不换行,c语言printf不换行
    本文目录一览:1、C语言不换行输入2、c语言的 ... [详细]
  • 本文介绍了为什么要使用多进程处理TCP服务端,多进程的好处包括可靠性高和处理大量数据时速度快。然而,多进程不能共享进程空间,因此有一些变量不能共享。文章还提供了使用多进程实现TCP服务端的代码,并对代码进行了详细注释。 ... [详细]
  • 本文介绍了Java的集合及其实现类,包括数据结构、抽象类和具体实现类的关系,详细介绍了List接口及其实现类ArrayList的基本操作和特点。文章通过提供相关参考文档和链接,帮助读者更好地理解和使用Java的集合类。 ... [详细]
  • 本文介绍了求a和b的最大公约数的计算方法,即使用gcd(a, b) = gcd(b, a%b)的公式进行计算。同时给出了一个具体的例子gcd(36, 24) = gcd(24, 12) = gcd(12, 0)。文章还给出了一个使用C语言编写的求最大公约数的程序,并解释了程序的实现原理。 ... [详细]
  • 本文讨论了一个数列求和问题,该数列按照一定规律生成。通过观察数列的规律,我们可以得出求解该问题的算法。具体算法为计算前n项i*f[i]的和,其中f[i]表示数列中有i个数字。根据参考的思路,我们可以将算法的时间复杂度控制在O(n),即计算到5e5即可满足1e9的要求。 ... [详细]
author-avatar
dsgfg
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有