动态规划之最长子序列问题

博客讨论了如何解决寻找一个序列的最长上升子序列问题,分别介绍了O(n²)和O(nlogn)两种算法。在O(n²)算法中,通过维护seqlen数组找到以每个元素为末位的最长子序列长度。而在O(nlogn)算法中,通过二分查找和替换策略更新答案数组ans,从而降低时间复杂度。博客提供了具体例子来解释这两种算法的工作原理,并给出了相关编程题目。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

一个数的序列bi,当b1 < b2 < … < bS的时候,我们称这个序列是上升的。
对于给定的一个序列(a1, a2, …, aN),我们可以得到一些上升的子序列(ai1, ai2, …, aiK),这里1 <= i1 < i2 < … < iK <= N。
比如,对于序列(1, 7, 3, 5, 9, 4, 8),有它的一些上升子序列,如(1, 7), (3, 4, 8)等等。这些子序列中最长的长度是4,比如子序列(1, 3, 5, 8).
我们的任务,就是对于给定的序列,求出最长上升子序列的长度。(最长不上升子序列就是正相反呗。)

下面我们以最长上升子序列为例研究。
其他非递增(减)等一系列问题就是以此为基础啊,比较的时候改改条件就行啊!
看清题目要求!
递增和不下降是不一样的啊摔!

O(n²)算法

seq[i]表示输入的第i个元素,seqlen[i]表示以第i个元素为末位的最长子序列长度。
在seqlen数组里选出最长,就是最长子序列长度啊。

O(nlogn)算法

arr[i]表示输入的数组元素,ans[len]表示生成长度为len时子序列的末位元素,len表示序列长度。
如果arr数组的新元素比ans数组的所有元素都大,把这个元素放在ans数组的最后,子序列长度+1;
如果arr数组的新元素比ans数组的最大元素小,比ans数组的其他元素小,那就用新元素替换掉这个最大元素。比如len=2时,ans[2]={1,5};若ans[i]即新元素为3,那就替换掉5,使得ans[2]={1,3},以保证有更多元素加入到序列中来。
所以我们发现,我们插入这个数据是通过替换来实现的,因而我们可以通过二分查找快速找到要替换的元素,从而将时间复杂度降低到O(nlogn)。

来举个栗子~
假设存在一个序列arr[1..9] = 2 1 5 3 6 4 8 9 7,可以看出来它的LIS长度为5。
下面一步一步试着找出它。
我们定义一个序列ans,然后令 i = 1 to 9 逐个考察这个序列。
此外,我们用一个变量Len来记录现在最长算到多少了
首先,把arr[1]有序地放到ans里,令ans[1] = 2,就是说当只有一个数字2的时候,长度为1的LIS的最小末尾是2。这时Len=1。

然后,把arr[2]有序地放到ans里,令ans[1] = 1,就是说长度为1的LIS的最小末尾是1,arr[1]=2已经没用了,这时Len=1。

接着,arr[3] = 5,arr[3]>ans[1],所以令ans[1+1]=ans[2]=arr[3]=5,就是说长度为2的LIS的最小末尾是5,很容易理解吧。这时候ans[1..2] = 1, 5,Len=2。

再来,arr[4] = 3,它正好夹在1,5之间,放在1的位置显然不合适,因为1小于3,长度为1的LIS最小末尾应该是1,这样很容易推知,长度为2的LIS最小末尾是3,于是可以把5淘汰掉,这时候ans[1..2] = 1, 3,Len = 2。

继续,arr[5] = 6,它在3后面,因为ans[2] = 3, 而6在3后面,于是很容易可以推知ans[3] = 6, 这时ans[1..3] = 1, 3, 6, Len = 3 。

第6个, arr[6] = 4,它在3和6之间,于是我们就可以把6替换掉,得到B[3] = 4。B[1..3] = 1, 3, 4, Len=3。

第7个, arr[7] = 8,它很大,比4大,于是ans[4] = 8。Len=4。

第8个, arr[8] = 9,得到ans[5] = 9,Len继续增大,L=5。

最后一个, arr[9] = 7,它在ans[3] = 4和ans[4] = 8之间,所以我们知道,最新的B[4] =7,B[1..5] = 1, 3, 4, 7, 9,Len = 5。

于是我们知道了LIS的长度为5。

ATTENTION!这个1,3,4,7,9不是LIS(真正的LIS是1,3,4,8,9或2,5,6,8,9),它只是存储的对应长度LIS的最小末尾。有了这个末尾,我们就可以一个一个地插入数据。

好啦,来个模板题→_→模板水题POJ 2533

Description

A numeric sequence of ai is ordered if a1 < a2 < … < aN. Let the subsequence of the given numeric sequence (a1, a2, …, aN) be any sequence (ai1, ai2, …, aiK), where 1 <= i1 < i2 < … < iK <= N. For example, sequence (1, 7, 3, 5, 9, 4, 8) has ordered subsequences, e. g., (1, 7), (3, 4, 8) and many others. All longest ordered subsequences are of length 4, e. g., (1, 3, 5, 8).

Your program, when given the numeric sequence, must find the length of its longest ordered subsequence.

Input

The first line of input file contains the length of sequence N. The second line contains the elements of sequence - N integers in the range from 0 to 10000 each, separated by spaces. 1 <= N <= 1000

Output

Output file must contain a single integer - the length of the longest ordered subsequence of the given sequence.

Sample Input

7
1 7 3 5 9 4 8

Sample Output

4

O(n²)算法

#include<cstdio>
#include<algorithm>
const int MAX=1010;
using namespace std;
int seq[MAX];
int seqlen[MAX];
int main()
{
    int i,j,n;
    int m;
    int maxn,maxlen=1;
    while(~scanf("%d",&n))
    {
        for(i=1; i<=n; i++)
        {
            seqlen[i]=1;
            scanf("%d",&seq[i]);
        }
        for (i=2; i<=n+1; i++)
        {
            maxn=0;
            for (j=1; j<=i-1; j++)
            {
                if(seq[j]<seq[i]&&seqlen[j]>maxn)
                    maxn=seqlen[j];
            }
            seqlen[i]=maxn+1;
            maxlen=max(maxlen,seqlen[i]);
        }
        printf("%d\n",maxlen);
    }
    return 0;
}

O(nlogn)算法

#include<cstdio>
#include<iostream>
#include<cstring>
const int MAXN=1010;
int arr[MAXN],ans[MAXN],i;
int seek(int num,int len)
{
    int low=1,high=len;
    while(low<=high)
    {
        int mid=(low+high)/2;
        if(num>=ans[mid]) low=mid+1;
        else high=mid-1;
    }
    return low;
}
int lis(int n)
{
    ans[1]=arr[1];
    int len=1;
    for(i=2; i<=n; ++i)
    {
        if(arr[i]>ans[len])
            ans[++len]=arr[i];
        else
        {
            int pos=seek(arr[i],len);
            ans[pos] = arr[i];
        }
    }
    return len;
}
int main()
{
    int T;
    while(~scanf("%d",&T))
    {
        for(i=1; i<=T; ++i)
            scanf("%d",&arr[i]);
        printf("%d\n",lis(T));
    }
    return 0;
}

ps:
好几天前看过的了,当时被韬神说的无地自容(虽然也没说什么○| ̄|_),现在序列还是心中的痛/(ㄒoㄒ)/~~
不过现在好几分会了QAQ

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值