应该把这个问题看成一个基本问题,感觉用动态规划的算法比较容易想到,也很不错,关于那个改进的O(nlogn)的算法有些不太明白,大部分动态规划都要寻求一个当前状态的最小值或最大值,如果按这样的思想,那不是所有的DP算法都可以降为O(nlogn)?

以下文章转载自CSDN,我收藏一下。因为找不到原作作者,敬请原谅,如果您是作者请告知我。

最长递增子序列问题的求解

最长递增子序列问题是一个很基本、较常见的小问题,但这个问题的求解方法却并不那么显而易见,需要较深入的思考和较好的算法素养才能得出良好的算法。由于这个问题能运用学过的基本的算法分析和设计的方法与思想,能够锻炼设计较复杂算法的思维,我对这个问题进行了较深入的分析思考,得出了几种复杂度不同算法,并给出了分析和证明。

一,    最长递增子序列问题的描述

设L=<a1,a2,…,an>是n个不同的实数的序列,L的递增子序列是这样一个子序列Lin=<aK1,ak2,…,akm>,其中k1<k2<…<km且aK1<ak2<…<akm。求最大的m值。

二,    第一种算法:转化为LCS问题求解

设序列X=<b1,b2,…,bn>是对序列L=<a1,a2,…,an>按递增排好序的序列。那么显然X与L的最长公共子序列即为L的最长递增子序列。这样就把求最长递增子序列的问题转化为求最长公共子序列问题LCS了。

最长公共子序列问题用动态规划的算法可解。设Li=< a1,a2,…,ai>,Xj=< b1,b2,…,bj>,它们分别为L和X的子序列。令C[i,j]为Li与Xj的最长公共子序列的长度。则有如下的递推方程:

这可以用时间复杂度为O(n2)的算法求解,由于这个算法上课时讲过,所以具体代码在此略去。求最长递增子序列的算法时间复杂度由排序所用的O(nlogn)的时间加上求LCS的O(n2)的时间,算法的最坏时间复杂度为O(nlogn)+O(n2)=O(n2)。

三,    第二种算法:动态规划法

设f(i)表示L中以ai为末元素的最长递增子序列的长度。则有如下的递推方程:

这个递推方程的意思是,在求以ai为末元素的最长递增子序列时,找到所有序号在L前面且小于ai的元素aj,即j<i且aj<ai。如果这样的元素存在,那么对所有aj,都有一个以aj为末元素的最长递增子序列的长度f(j),把其中最大的f(j)选出来,那么f(i)就等于最大的f(j)加上1,即以ai为末元素的最长递增子序列,等于以使f(j)最大的那个aj为末元素的递增子序列最末再加上ai;如果这样的元素不存在,那么ai自身构成一个长度为1的以ai为末元素的递增子序列。

这个算法由Java实现的代码如下:

public void lis(float[] L)

{

int n = L.length;

int[] f = new int[n];//用于存放f(i)值;

f[0]=1;//以第a1为末元素的最长递增子序列长度为1;

for(int i = 1;i<n;i++)//循环n-1次

{

f[i]=1;//f[i]的最小值为1;

for(int j=0;j<i;j++)//循环i 次

{

if(L[j]<L[i]&&f[j]>f[i]-1)

f[i]=f[j]+1;//更新f[i]的值。

}

}

System.out.println(f[n-1]);

}

这个算法有两层循环,外层循环次数为n-1次,内层循环次数为i次,算法的时间复杂度

所以T(n)=O(n2)。这个算法的最坏时间复杂度与第一种算法的阶是相同的。但这个算法没有排序的时间,所以时间复杂度要优于第一种算法。

四,    对第二种算法的改进

在第二种算法中,在计算每一个f(i)时,都要找出最大的f(j)(j<i)来,由于f(j)没有顺序,只能顺序查找满足aj<ai最大的f(j),如果能将让f(j)有序,就可以使用二分查找,这样算法的时间复杂度就可能降到O(nlogn)。于是想到用一个数组B来存储“子序列的”最大递增子序列的最末元素,即有

B[f(j)] = aj

在计算f(i)时,在数组B中用二分查找法找到满足j<i且B[f(j)]=aj<ai的最大的j,并将B[f[j]+1]置为ai。下面先写出代码,再证明算法的证明性。用Java实现的代码如下:

lis1(float[] L)

{

int n = L.length;

float[] B = new float[n+1];//数组B;

B[0]=-10000;//把B[0]设为最小,假设任何输入都大于-10000;

B[1]=L[0];//初始时,最大递增子序列长度为1的最末元素为a1

int Len = 1;//Len为当前最大递增子序列长度,初始化为1;

int p,r,m;//p,r,m分别为二分查找的上界,下界和中点;

for(int i = 1;i<n;i++)

{

p=0;r=Len;

while(p<=r)//二分查找最末元素小于ai+1的长度最大的最大递增子序列;

{

m = (p+r)/2;

if(B[m]<L[i]) p = m+1;

else r = m-1;

}

B[p] = L[i];//将长度为p的最大递增子序列的当前最末元素置为ai+1;

if(p>Len) Len++;//更新当前最大递增子序列长度;

}

System.out.println(Len);

}

现在来证明这个算法为什么是正确的。要使算法正确只须证如下命题:

命题1:每一次循环结束数组B中元素总是按递增顺序排列的。

证明:用数学归纳法,对循环次数i进行归纳。

当i=0时,即程序还没进入循环时,命题显然成立。

设i<k时命题成立,当i=k时,假设存在j1<j2,B[j1]>B[j2],因为第i次循环之前数组B是递增的,因此第i次循环时B[j1]或B[j2]必有一个更新,假设B[j1]被更新为元素ai+1,由于ai+1=B[j1]> B[j2],按算法ai+1应更新B[j2]才对,因此产生矛盾;假设B[j2]被更新,设更新前的元素为s,更新后的元素为ai+1,则由算法可知第i次循环前有B[j2]=s< ai+1< B[j1],这与归纳假设矛盾。命题得证。

命题2B[c]中存储的元素是当前所有最长递增子序列长度为c的序列中,最小的最末元素,即设当前循环次数为i,有B[c]={aj| f(k)=f(j)=c∧k,j≤i+1→aj≤ak}(f(i)为与第二种算法中的f(i)含义相同)。

证明:程序中每次用元素ai更新B[c]时(c=f(i)),设B[c]原来的值为s,则必有ai<s,不然ai就能接在s的后面形成长度为c+1的最长递增子序列,而更新B[c+1]而不是B[c]了。所有B[c]中存放的总是当前长度为c的最长递增子序列中,最小的最末元素。

命题3设第i次循环后得到的p为p(i+1),那么p(i)为以元素ai为最末元素的最长递增子序列的长度。

证明:只须证p(i)等于第二种算法中的f(i)。显然一定有p(i)<=f(i)。假设p(i)<f(i),那么有两种情况,第一种情况是由二分查找法找到的p(i)不是数组B中能让ai接在后面成为新的最长递增子序列的最大的元素,由命题1和二分查找的方法可知,这是不可能的;第二种情况是能让ai接在后面形成长于p(i)的最长递增子序列的元素不在数组B中,由命题2可知,这是不可能的,因为B[c]中存放的是最末元素最小的长度为c的最长递增子序列的最末元素,若ai能接在长度为L(L> p(i))的最长递增子序列后面,就应该能接在B[L]后面,那么就应该有p(i)=L,与L> p(i)矛盾。因此一定有p(i)=f(i),命题得证。

算法的循环次数为n,每次循环二分查找用时logn,所以算法的时间复杂度为O(nlogn)。这个算法在第二种算法的基础上得到了较好的改进。

五,    总结

本论文只给出了计算解的大小而没有给出构造解的方法,因为我认为计算解的大小的算法已能给出对问题的本质认识,只要计算解大小的算法设计出,构造解就只是技术细节的问题了,而我关心的是怎样对问题得到很好的认识而设计出良好的算法。以上几种算法已用Java实现,都能得到正确的结果。在设计和改进算法时用到了基本的算法设计和分析、证明的基本方法,很好的锻炼了设计与分析算法的思维能力,让我从感性上认识到算法分析与设计的重要性,并且感受了算法分析、设计和改进的乐趣。

动态规划-- 数组最大不连续递增子序列相关推荐

  1. 动态规划之最长递增子序列 最长不重复子串 最长公共子序列

    [前言]动态规划:与分治法相似,即通过组合子问题来求解原问题,不同的是分治法是将问题划分为互不相交的子问题,递归求解子问题,再将他们组合起来求出原问题的解. 动态规划则应用于子问题重叠的情况,通常用来 ...

  2. 算法:经典题五 题目五 信封套娃层数问题 转化为 数组最长递增子序列问题

    输入:一组信封长宽信息 二维数组(假设都是长大于宽的) 输出:套信封层数最多的 信封个数,最多套多少层娃 要点: 第一纬度信息,从小到大排序,第二维信息再从大到小排序,求第二维的最长递增子序列长度就是 ...

  3. 动态规划(最长递增子序列)---最长递增子序列

    最长递增子序列 300. Longest Increasing Subsequence (Medium) 题目描述:   给定一个数组,找到它的最长递增子序列 思路分析:   动态规划思想,定义一个数 ...

  4. 动态规划应用--最长递增子序列 LeetCode 300

    文章目录 1. 问题描述 2. 解题思路 2.1 动态规划 2.2 二分查找 1. 问题描述 有一个数字序列包含n个不同的数字,如何求出这个序列中的最长递增子序列长度?比如2,9,3,6,5,1,7这 ...

  5. 动态规划算法 | 最长递增子序列

    通过查阅相关资料发现动态规划问题一般就是求解最值问题.这种方法在解决一些问题时应用比较多,比如求最长递增子序列等. 有部分人认为动态规划的核心就是:穷举.因为要求最值,肯定要把所有可行的答案穷举出来, ...

  6. 牛客刷题动态规划之最长递增子序列

    题目描述 给定数组arr,设长度为n,输出arr的最长递增子序列.(如果有多个答案,请输出其中字典序最小的) 示例1 输入 [2,1,5,3,6,4,8,9,7] 输出 [1,3,4,8,9] 示例2 ...

  7. 动态规划算法04-最长递增子序列问题

    最长递增子序列问题 简述 经典的动态规划问题. 问题描述 给定一个序列,求解其中长度最长的递增子序列. 问题分析 这种可以向下查询答案的很容易想到动态规划的解法. 要求长度为i的序列Ai={a1,a2 ...

  8. 动态规划(最长递增子序列)---最长摆动子序列

    最长摆动子序列 376. Wiggle Subsequence (Medium) 题目描述: 给定一个数组,求出其最长摆动子序列 Input: [1,7,4,9,2,5] Output: 6 The ...

  9. 程序员面试100题之十二:求数组中最长递增子序列

    写一个时间复杂度尽可能低的程序,求一个一维数组(N个元素)中最长递增子序列的长度. 例如:在序列1,-1,2,-3,4,-5,6,-7中,其最长递增子序列为1,2,4,6. 分析与解法 根据题目要求, ...

最新文章

  1. ActiveMQ—消息特性(延迟和定时消息投递)
  2. opencv--车牌识别
  3. Gulp快速入门教程
  4. Linux cgroup机制分析之cpuset subsystem
  5. Apollo配置发布原理
  6. 面向对象设计与构造第一次总结作业
  7. treemap怎么保证有序_干货!208道面试题教你怎么通过面试!
  8. win10语音语言服务器,win10系统:朗读女语音库(发音人)安装方法说明
  9. Jmeter模拟多用户同时登陆
  10. 力扣算法题—069x的平方根
  11. 因使用盗版软件赔偿 869 万元 :CIO被开除、罚 15 万元
  12. pdf页面倒序如何调整?
  13. idear怎么设置自动导包
  14. WiFi穿墙手册:解读天线、dbi、发射功率和无线信号的关系
  15. 软件测试自动化验证码,借助 OCR,协助绕过 web 自动化测试中一些简单验证码问题。...
  16. SideWinder诱饵文档加密流量分析
  17. 在 JavaScript 中对数组进行 for-each
  18. python中type(),dtype(),astype()的区别
  19. 2020cvpr最佳人脸识别-Suppressing Uncertainties for Large-Scale Facial Expression Recognition
  20. OSEA中QRS波检测算法代码分析-未完待续

热门文章

  1. 实例 | 分析38万条数据,用Python分析保险产品交叉销售和哪些因素有关
  2. frp点对点udp方式内网穿透ssh,不走服务器流量
  3. 该shi的垃圾短信,为何屡禁不止?有何猫腻?
  4. 360P2建html网站,360P2刷机
  5. 美国2020年攻破艾滋病_2020年美国和欧洲的游戏状况
  6. 一家企业,从创办到倒闭,一共需要用到多少独立系统或软件?
  7. RabbitMQ消费者莫名丢失的问题解决
  8. android声音录制音量太小,为什么总是感觉手机音量太小?跟我这样设置,声音瞬间大上许多...
  9. vue级联选择框(Cascader)动态渲染数据
  10. 正则例子---爬取内涵段子