最长递增子序列

来源:互联网 发布: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的长度

  1. 首先,把arr[1]放到seq里,令seq[1] = 3,就是说当只有一个数字3时,长度为1的LIS的最小末尾是3,这时len为1
  2. 然后,把arr[2]有序的放到seq里,令seq[1] = 2,就是说长度为1的LIS最小末尾是1,seq[1] = 3已经没用了,这时len仍为1(ps:应该比较好理解)
  3. 接着,arr[3] = 6, arr[3] > seq[1],所以令seq[1 + 1] = seq[2] = arr[3] = 6,也就是说长度为2的LIS的最小末尾是6,这时len为2
  4. 再来,arr[4] = 4,它正好在2, 6之间,放在2的位置显然不合适,因为2  < 4,长度为1的LIS最小末尾应该为2,这样很容易推理出长度为2的LIS最小末尾应该为4,于是把6淘汰,这时seq[2] = {2, 4}, len为2
  5. 继续,arr[5] = 7,它在seq[2]后面,因此seq[2 + 1] = seq[3] = 7,这时len为3
  6. 第6个,arr[6] = 5, 它在4和7之间,因此把7替换掉,seq[3] = {2, 4, 5}, len为3
  7. 第7个,arr[7] = 9, 它在seq[3]后面,因此seq[4] = {2, 4, 5, 9}, len为4
  8. 第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名,得瑟一下:



这里可以看到,高手都是苦逼过来的,继续加油!



原创粉丝点击