最长递增子序列
来源:互联网 发布:linux怎么修改用户组 编辑:程序博客网 时间:2024/05/16 16:56
概述
最长递增子序列(Longest Increasing Subsequence)长度有很多种解决方法,这里介绍两种,一种是动态规划的实现,一种是O(NlogN)的解决方法(参考编程之美)
动态规划
我们假定w0,w1,...,wn-1为一串正整数序列,前i个数的最长递增子序列的长度为A(i),则最优解结构为:
- A(0) = 1
- A(i) = max{A(j) + 1, 0 <= j < i}
时间复杂度为O(n * n)
实现代码也很简单,贴一下c的实现代码(11月7日增加了打印最长递增子序列的代码)
题目
题目描述:给定一个整数序列,请问如何去掉最少的元素使得原序列变成一个全递增的序列。输入:输入的第一行包括一个整数N(1<=N<=10000)。接下来的一行是N个满足题目描述条件的整数。输出:可能有多组测试数据,对于每组数据,输出去掉最少的元素后的全递增序列。样例输入:8186 186 150 200 160 130 197 220样例输出:150 160 197 220提示:如果有多个结果序列满足条件,输出相对位置靠前的那个序列。
ac代码
#include <stdio.h>#include <string.h>#include <stdlib.h> #define N 10050 int dp[N], seq[N], pre[N];int count, maxindex; int longIS(int *arr, int n){ int i, j, lis; dp[0] = 1; memset(pre, 0, sizeof(pre)); for (i = 1, lis = 1, maxindex = 0; i < n; i ++) { dp[i] = 1; for (j = 0; j < i; j ++) { if (arr[j] < arr[i] && dp[j] + 1 > dp[i]) { dp[i] = dp[j] + 1; pre[i] = j; } } if (dp[i] > lis) { lis = dp[i]; maxindex = i; } } return lis;} void outputLis(int *arr, int lis){ for (count = 0; lis > 0; lis --, count ++) { seq[count] = arr[maxindex]; maxindex = pre[maxindex]; }} int main(void){ int i, n, lis, *arr; while (scanf("%d", &n) != EOF) { arr = (int *)malloc(sizeof(int) * n); for (i = 0; i < n; i ++) { scanf("%d", arr + i); } lis = longIS(arr, n); outputLis(arr, lis); for (i = count - 1; i >= 0; i --) { if (i == 0) { printf("%d\n", seq[i]); } else { printf("%d ", seq[i]); } } free(arr); arr = NULL; } return 0;}/************************************************************** Problem: 1262 User: wangzhengyi Language: C Result: Accepted Time:20 ms Memory:1032 kb****************************************************************/
O(nLogn)算法实现
这里参考了别人的实现算法,原文链接:http://www.felix021.com/blog/read.php?1587
算法思路
这里举一个例子来讲解这个算法,假设一个序列 arr[8] = {3, 2, 6, 4, 7, 5, 9, 10}, 可以看出这里的LIS长度为5.我们尝试一步步的推理一下:
我们定义一个序列seq,然后另i = 1 to 8考察这个序列,同时用len记录LIS的长度
- 首先,把arr[1]放到seq里,令seq[1] = 3,就是说当只有一个数字3时,长度为1的LIS的最小末尾是3,这时len为1
- 然后,把arr[2]有序的放到seq里,令seq[1] = 2,就是说长度为1的LIS最小末尾是1,seq[1] = 3已经没用了,这时len仍为1(ps:应该比较好理解)
- 接着,arr[3] = 6, arr[3] > seq[1],所以令seq[1 + 1] = seq[2] = arr[3] = 6,也就是说长度为2的LIS的最小末尾是6,这时len为2
- 再来,arr[4] = 4,它正好在2, 6之间,放在2的位置显然不合适,因为2 < 4,长度为1的LIS最小末尾应该为2,这样很容易推理出长度为2的LIS最小末尾应该为4,于是把6淘汰,这时seq[2] = {2, 4}, len为2
- 继续,arr[5] = 7,它在seq[2]后面,因此seq[2 + 1] = seq[3] = 7,这时len为3
- 第6个,arr[6] = 5, 它在4和7之间,因此把7替换掉,seq[3] = {2, 4, 5}, len为3
- 第7个,arr[7] = 9, 它在seq[3]后面,因此seq[4] = {2, 4, 5, 9}, len为4
- 第8个,arr[8] = 10,它在seq[4]后面,因此seq[5] = {2, 4, 5, 9, 10}, len为5
同时,可以发现每次往seq插入数据时,seq是有序的,也就是说我们可以用二分查找算法确定每次插入的位置,于是算法复杂度也就为O(NlogN)
实现代码
#include <stdio.h>#include <stdlib.h>void longest_increasing_seq(int *arr, int n){int i, len, left, right, mid, *seq;seq = (int *)malloc(sizeof(int) * n);// 初始化seq[0] = arr[0];len = 1;for (i = 1; i < n; i ++) {if (arr[i] > seq[len - 1]) { // arr[i]比seq序列所有数字都大,则插入到seq末尾,len自加1seq[len] = arr[i];len += 1;} else {left = 0, right = len - 1;while (left <= right) {mid = left + ((right - left) >> 1);// 防止溢出if (arr[i] < seq[mid])right = mid - 1;else if (arr[i] > seq[mid])left = mid + 1;elsebreak;}seq[mid] = arr[i];}}printf("%d\n", len);free(seq);}int main(void){int i, n, *arr;while (scanf("%d", &n) != EOF) {arr = (int *)malloc(sizeof(int) * n);for (i = 0; i < n; i ++)scanf("%d", arr + i);longest_increasing_seq(arr, n);free(arr);}return 0;}
起因
说下起因吧,也是因为在九度oj做题目,一道明显最长递增字串的题目,用动态规划做的时候,5个case只能通过三个,第4个case超时了,悲剧,所以学习了一种新的求最长递增字串的方法,挺不错的,顺便复习了二分查找(ps:注意我二分查找的几处细节,据说90%的程序员无法写出正确的二分查找算法,但是我一定是那百分之10以内的)
题目描述
题目描述:在读高中的时候,每天早上学校都要组织全校的师生进行跑步来锻炼身体,每当出操令吹响时,大家就开始往楼下跑了,然后身高矮的排在队伍的前面,身高较高的就要排在队尾。突然,有一天出操负责人想了一个主意,想要变换一下队形,就是当大家都从楼上跑下来后,所有的学生都随机地占在一排,然后出操负责人从队伍中抽取出一部分学生,使得队伍中剩余的学生的身高从前往后看,是一个先升高后下降的“山峰”形状。据说这样的形状能够给大家带来好运,祝愿大家在学习的道路上勇攀高峰。(注,山峰只有一边也符合条件,如1,1、2,2、1均符合条件)输入:输入可能包含多个测试样例。对于每个测试案例,输入的第一行是一个整数n(1<=n<=1000000):代表将要输入的学生个数。输入的第二行包括n个整数:代表学生的身高(cm)(身高为不高于200的正整数)。输出:对应每个测试案例,输出需要抽出的最少学生人数。样例输入:6100 154 167 159 132 1055152 152 152 152 152样例输出:04
动态规划的问题
用O(n * n)的解法会超时,因此改用二分查找这种解决方法
#include <stdio.h>#include <stdlib.h>#include <string.h> void lis(int *queue, int n){ int i, k, l, r, mid, max, *seq, *left, *right; seq = (int *)malloc(sizeof(int) * n); left = (int *)malloc(sizeof(int) * n); right = (int *)malloc(sizeof(int) * n); seq[0] = queue[0]; left[0] = 1; k = 1; for (i = 1; i < n; i ++) { if (queue[i] > seq[k - 1]) { left[i] = k + 1; seq[k] = queue[i]; k += 1; } else { left[i] = k; l = 0, r = k - 1; while (l <= r) { mid = (l + r) >> 1; if (seq[mid] > queue[i]) r = mid - 1; else if (seq[mid] < queue[i]) l = mid + 1; else { l = mid; break; } } seq[l] = queue[i]; } } /* 打印测试 for (i = 0; i < n; i ++) printf("%d ", left[i]); printf("\n"); */ memset(seq, 0, sizeof(seq)); seq[0] = queue[n - 1]; right[n - 1] = 1; k = 1; for (i = n - 2; i >= 0; i --) { if (queue[i] > seq[k - 1]) { right[i] = k + 1; seq[k] = queue[i]; k += 1; } else { right[i] = k; l = 0, r = k - 1; while (l <= r) { mid = (l + r) >> 1; if (seq[mid] > queue[i]) r = mid - 1; else if (seq[mid] < queue[i]) l = mid + 1; else { l = mid; break; } } seq[l] = queue[i]; } } /* 打印测试 for (i = 0; i < n; i ++) printf("%d ", right[i]); printf("\n"); */ for (i = 0, max = 0; i < n; i ++) { if (left[i] + right[i] - 1 > max) max = left[i] + right[i] - 1; } printf("%d\n", n - max); free(seq); free(left); free(right);} int main(void){ int i, n, *queue; while (scanf("%d", &n) != EOF) { // 初始化 queue = (int *)malloc(sizeof(int) * n); for (i = 0; i < n; i ++) scanf("%d", queue + i); lis(queue, n); free(queue); } return 0;} /************************************************************** Problem: 1500 User: wangzhengyi Language: C Result: Accepted Time:950 ms Memory:16544 kb****************************************************************/
后记
本来早就该发表这篇文章,无奈二分查找赋值那里浪费了一些时间,不过ac了这到题目我进入了九度oj排行榜的第10名,得瑟一下:
这里可以看到,高手都是苦逼过来的,继续加油!
- 最长递增子序列
- 最长递增子序列
- 最长递增子序列
- 最长递增子序列
- 最长递增子序列
- 最长递增子序列
- 最长递增子序列
- 最长递增子序列
- 最长递增子序列
- 最长递增子序列
- 最长递增子序列
- 最长递增子序列
- 最长递增子序列
- 最长递增子序列
- 最长递增子序列
- 最长递增子序列
- 最长递增子序列
- 最长递增子序列
- json 返回的string有反斜杠 一种原因
- Eclipse如何生成带有自定tag的Java Doc
- C/C++中容易造成内存溢出的函数
- Java设计模式之代理模式
- hdu1232 畅通工程
- 最长递增子序列
- Linux下搭建ORACLE数据库
- 扒一扒这个数据挖掘行业
- 程序员扩充人脉那些事儿
- Android NDK调用c/c++
- 黑马程序员_面向对象--继承、多态
- 杭电acm试题分类
- 菜单关联步骤
- java经典面试题