最长递增子序列(LIS)(转)
最长递增子序列(LIS)
---
最长递增子序列又叫做最长上升子序列;子序列,正如LCS一样,元素不一定要求连续。本节讨论实现三种常见方法,主要是练手。
题:求一个一维数组arr[i]中的最长递增子序列的长度,如在序列1,-1,2,-3,4,-5,6,-7中,最长递增子序列长度为4,可以是1,2,4,6,也可以是-1,2,4,6。
方法一:DP
像LCS一样,从后向前分析,很容易想到,第i个元素之前的最长递增子序列的长度要么是1(单独成一个序列),要么就是第i-1个元素之前的最长递增子序列加1,可以有状态方程:
LIS[i] = max{1,LIS[k]+1},其中,对于任意的k<=i-1,arr[i] > arr[k],这样arr[i]才能在arr[k]的基础上构成一个新的递增子序列。
代码如下:在计算好LIS长度之后,output函数递归输出其中的一个最长递增子序列。
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
|
#include <iostream>using namespace std;/* 最长递增子序列 LIS * 设数组长度不超过 30 * DP*/int dp[31]; /* dp[i]记录到[0,i]数组的LIS */int lis; /* LIS 长度 */int LIS(int * arr, int size){ for(int i = 0; i < size; ++i) { dp[i] = 1; for(int j = 0; j < i; ++j) { if(arr[i] > arr[j] && dp[i] < dp[j] + 1) { dp[i] = dp[j] + 1; if(dp[i] > lis) { lis = dp[i]; } } } } return lis;}/* 输出LIS */void outputLIS(int * arr, int index){ bool isLIS = 0; if(index < 0 || lis == 0) { return; } if(dp[index] == lis) { --lis; isLIS = 1; } outputLIS(arr,--index); if(isLIS) { printf("%d ",arr[index+1]); }}void main(){ int arr[] = {1,-1,2,-3,4,-5,6,-7}; /* 输出LIS长度; sizeof 计算数组长度 */ printf("%d\n",LIS(arr,sizeof(arr)/sizeof(int))); /* 输出LIS */ outputLIS(arr,sizeof(arr)/sizeof(int) - 1); printf("\n");} |
这个方法也最容易想到也是最传统的解决方案,对于该方法和LIS,有以下两点说明:
- 由LIS可以衍生出来最长非递减子序列,最长递减子序列,道理是一样的。
- 对于输出序列,也是可以再申请一数组pre[i]记录子序列中array[i]的前驱,道理跟本节的实现也是一样的
方法二:排序+LCS
这个方法是在Felix’blog(见参考资料)中看到的,因为简单,他在博文中只是提了一句,不过为了练手,虽然懒,还是硬着头皮写一遍吧,正好再写一遍快排,用quicksort + LCS,这个思路还是很巧妙的,因为LIS是单调递增的性质,所以任意一个LIS一定跟排序后的序列有LCS,并且就是LIS本身。代码如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
|
#include <iostream>using namespace std;/* 最长递增子序列 LIS * 设数组长度不超过 30 * quicksort + LCS*/void swap(int * arr, int i, int j){ int tmp = arr[i]; arr[i] = arr[j]; arr[j] = tmp;}void qsort(int * arr, int left, int right){ if(left >= right) return ; int index = left; for(int i = left+1; i <= right; ++i) { if(arr[i] < arr[left]) { swap(arr,++index,i); } } swap(arr,index,left); qsort(arr,left,index-1); qsort(arr,index+1,right);}int dp[31][31];int LCS(int * arr, int * arrcopy, int len){ for(int i = 1; i <= len; ++i) { for(int j = 1; j <= len; ++j) { if(arr[i-1] == arrcopy[j-1]) { dp[i][j] = dp[i-1][j-1] + 1; }else if(dp[i-1][j] > dp[i][j-1]) { dp[i][j] = dp[i-1][j]; }else { dp[i][j] = dp[i][j-1]; } } } return dp[len][len];}void main(){ int arr[] = {1,-1,2,-3,4,-5,6,-7}; int arrcopy [sizeof(arr)/sizeof(int)]; memcpy(arrcopy,arr,sizeof(arr)); qsort(arrcopy,0,sizeof(arr)/sizeof(int)-1); /* 计算LCS,即LIS长度 */ int len = sizeof(arr)/sizeof(int); printf("%d\n",LCS(arr,arrcopy,len));} |
方法三:DP+二分查找
《编程之美》对于这个方法有提到,不过它的讲解我看得比较难受,好长时间才明白,涉及到的数组也比较多,除了源数据数组,有LIS[i]和MaxV[LIS[i]],后来看了大牛Felix的讲解,我才忽然发现编程之美中的这个数组MaxV[LIS[i]]在记录信息上其实是饶了弯的,因为我们在寻找某一长度子序列所对应的最大元素最小值时,完全没必要通过LIS[i]去定位,即没必要与数据arr[i]挂钩,直接将MaxV[i]的下标作为LIS的长度,来记录最小值就可以了(表达能力太次,囧。。。),一句话,就是不需要LIS[i]这个数组了,只用MaxV[i]即可达到效果,而且原理容易理解,代码表达也比较直观、简单。
代码如下:
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
|
#include <iostream>using namespace std;/* 最长递增子序列 LIS * 设数组长度不超过 30 * DP + BinarySearch*/int MaxV[30]; /* 存储长度i+1(len)的子序列最大元素的最小值 */int len; /* 存储子序列的最大长度 即MaxV当前的下标*//* 返回MaxV[i]中刚刚大于x的那个元素的下标 */int BinSearch(int * MaxV, int size, int x){ int left = 0, right = size-1; while(left <= right) { int mid = (left + right) / 2; if(MaxV[mid] < x) { left = mid + 1; }else { right = mid - 1; } } return left;}int LIS(int * arr, int size){ MaxV[0] = arr[0]; /* 初始化 */ len = 1; for(int i = 1; i < size; ++i) /* 寻找arr[i]属于哪个长度LIS的最大元素 */ { if(arr[i] > MaxV[len-1]) /* 大于最大的自然无需查找,否则二分查其位置 */ { MaxV[len++] = arr[i]; }else { int pos = BinSearch(MaxV,len,arr[i]); MaxV[pos] = arr[i]; } } return len;}void main(){ int arr[] = {1,-1,2,-3,4,-5,6,-7}; /* 计算LIS长度 */ printf("%d\n",LIS(arr,sizeof(arr)/sizeof(int)));} |
下面说说原理:
目的:我们期望在前i个元素中的所有长度为len的递增子序列中找到这样一个序列,它的最大元素比arr[i+1]小,而且长度要尽量的长,如此,我们只需记录len长度的递增子序列中最大元素的最小值就能使得将来的递增子序列尽量地长。
方法:维护一个数组MaxV[i],记录长度为i的递增子序列中最大元素的最小值,并对于数组中的每个元素考察其是哪个子序列的最大元素,二分更新MaxV数组,最终i的值便是最长递增子序列的长度。这个方法真是太巧妙了,妙不可言。
代码如下:
最长递增子序列(LIS)(转)的更多相关文章
- 2.16 最长递增子序列 LIS
[本文链接] http://www.cnblogs.com/hellogiser/p/dp-of-LIS.html [分析] 思路一:设序列为A,对序列进行排序后得到B,那么A的最长递增子序列LIS就 ...
- 动态规划(DP),最长递增子序列(LIS)
题目链接:http://poj.org/problem?id=2533 解题报告: 状态转移方程: dp[i]表示以a[i]为结尾的LIS长度 状态转移方程: dp[0]=1; dp[i]=max(d ...
- 最长回文子序列LCS,最长递增子序列LIS及相互联系
最长公共子序列LCS Lintcode 77. 最长公共子序列 LCS问题是求两个字符串的最长公共子序列 \[ dp[i][j] = \left\{\begin{matrix} & max(d ...
- 一个数组求其最长递增子序列(LIS)
一个数组求其最长递增子序列(LIS) 例如数组{3, 1, 4, 2, 3, 9, 4, 6}的LIS是{1, 2, 3, 4, 6},长度为5,假设数组长度为N,求数组的LIS的长度, 需要一个额外 ...
- 算法面试题 之 最长递增子序列 LIS
找出最长递增序列 O(NlogN)(不一定连续!) 参考 http://www.felix021.com/blog/read.php?1587%E5%8F%AF%E6%98%AF%E8%BF%9E%E ...
- 算法之动态规划(最长递增子序列——LIS)
最长递增子序列是动态规划中最经典的问题之一,我们从讨论这个问题开始,循序渐进的了解动态规划的相关知识要点. 在一个已知的序列 {a1, a 2,...an}中,取出若干数组成新的序列{ai1, ai ...
- 最长递增子序列 LIS 时间复杂度O(nlogn)的Java实现
关于最长递增子序列时间复杂度O(n^2)的实现方法在博客http://blog.csdn.net/iniegang/article/details/47379873(最长递增子序列 Java实现)中已 ...
- 动态规划 - 最长递增子序列(LIS)
最长递增子序列是动态规划中经典的问题,详细如下: 在一个已知的序列{a1,a2,...,an}中,取出若干数组组成新的序列{ai1,ai2,...,aim},其中下标i1,i2,...,im保持递增, ...
- 最长递增子序列LIS再谈
DP模型: d(i) 以第 i 个元素结尾的最长递增子序列的长度. 那么就有 d(i) = max(d(j)) + 1;(j<i&&a[j]<a[i]),答案 max(d( ...
- POJ 1836 Alignment 最长递增子序列(LIS)的变形
大致题意:给出一队士兵的身高,一开始不是按身高排序的.要求最少的人出列,使原序列的士兵的身高先递增后递减. 求递增和递减不难想到递增子序列,要求最少的人出列,也就是原队列的人要最多. 1 2 3 4 ...
随机推荐
- iOS- CALayer绘图,如何绘制渐变效果图
1.渐变效果 —> 色彩空间 首先:自定义一个渐变视图,在自定义视图中重写drawRect #import "ViewController.h" #import " ...
- CSS和JS引用图片(资源)的路径问题
做项目时遇到了这个问题,特此写个笔记记一下
- vi 基础配置
" Configuration file for vim " CVE-2007-2438 " Normally we use vim-extensions. If you ...
- VBA笔记(一)——基础配置
开启VBA编程环境——VBE 方法一:按<Alt+F11>组合建 方法二:查看代码 宏设置 当然启用宏的设置方式不同,宏的启动方式也不一样. 首先打开“office 按钮”,选择“exce ...
- 解决Slave SQL线程Waiting for binlog lock
最近在我们线上库物理备份的时候出现一个奇怪的现象: 我们备份都在从库上备份的,在业务低一般是在晚上2点钟开始备份.有天发现从库的延迟一直在增加,登录上实例,通过show processli ...
- Qt——线程与定时器
一.定时器QTimer类 The QTimer class provides repetitive and single-shot timers. The QTimer class provides ...
- D-Separation(D分离)-PRML-8.22-Graphical Model 五 18 by 小军
D-Separation(D分离)-PRML-8.22-Graphical Model 五18by 小军 一.引言 在贝叶斯网络的学习过程中,经常会遇到(D-Separation)D-分离这个概念 ...
- 【算法复习】codevs1022 匈牙利算法
题目描述 Description 有一个N×M的单位方格中,其中有些方格是水塘,其他方格是陆地.如果要用1×2的矩阵区覆盖(覆盖过程不容许有任何部分重叠)这个陆地,那么最多可以覆盖多少陆地面积. ...
- Luogu 3373 又乘又加的线段树
Luogu 3373 又乘又加的线段树 当给一个节点加上一个加法标记时,直接把加法标记 += 新值: 当给一个节点加上一个乘法标记时,把乘法标记和加法标记同时 *= 新值.(注意pushdown函数中 ...
- STS导入Gradle项目出现 Could not create task of type 'DependencyManagementReportTask'
解决方法: 在build.gradle文件下添加如下: classpath 'io.spring.gradle:dependency-management-plugin:0.6.1.RELEASE'