最长单调递增子序列——动态规划算法

题目简介

最长单调递增子序列是一个很经典的问题,他所需要的就是要在整个序列中(序列一般来讲应该是无序的)寻找出一个最长的升序子序列,这个子序列中的所有元素都必须来自这个原序列,它们可以在原序列中是不相邻的,但是它们之间的相对位置是不可以改动的。比如一个序列:

(1, 7, 3, 5, 9, 4, 8)

它有一些上升子序列,如(1, 7), (3, 4, 8)等等。在这些子序列中,长度最长的子序列的长度为4,比如(1,3,5,9)或者(1,3,5,8),这两个都是最长的升序子序列,所以对于一个给定的序列,它的最长升序子序列可能不唯一,在本题目中我们不考虑列举出所有的最长升序子序列,我们只考虑如何找出最长升序子序列的长度。

穷举法?过于复杂

没错,很多问题在穷举法面前都能做到实现,穷举法最强大的优点就在于它实在是太普适了,我们完全可以用穷举法把所有的升序子序列全部找出来,然后找到元素最多的子序列,将长度进行输出,但是这么做带来的弊端就是太过于复杂,如果原序列本身就是一个严格单调递增的子序列,那用穷举法所需要的的时间就是2^n,这个速度有些拉跨,或者说过于拉胯。

转化为最长公共子序列问题?是个妙招

关于最长公共子序列的问题我再之后会继续更新博客,现在我们只是将它看作一个已经写好的算法,这个算法的本质就是寻找两个序列A,B它们之间最长的公共子序列,这个公共子序列的每一个元素必须既存在于A,也存在与B,它们之间可以不相邻,但是他们的相对顺序不能改变,这些要求都和最长公共子序列的要求基本一致,只是没有升序的要求,那么我们怎么把升序的要求加到这个公共子序列算法中呢?

没错,我们可以先把原序列进行排序。存储到B序列中,A序列(原序列)和B序列(排序后的序列)这两个序列再进行最长公共子序列问题的求解,这样求解出来的最长公共子序列就是原序列的最长递增子序列了。

单独考虑-动态规划

其实最长公共子序列的本质我们也是根据动态规划的想法进行计算的。在这里我们只考虑单调递增子序列的独立问题求解方法。

我们可以发现这个问题具有最优子结构性质,也就是说我们可以把这个问题拆分成若干个存在重复情况的子问题进行求解。

如果我们想查找序列1,7,3,5,9,4,8的最长公共子序列,可以划分为寻找1,7,3,5,9,4的最长公共子序列(在这里称作子问题1),末尾再加上8,看前面的子问题1中获得的最长公共子序列答案最后一个元素是否小于8,如果小于8,那么原问题的解就是子问题1的解后面加上8,如果子问题1的解最后一个元素大于8,那么原问题的解就是子问题1的解。如果存在两个相同长度的解,并且一个最后元素大于8,一个小于8,那么我们便选择小于8的解最后在加上8即可。

状态转移方程如下(dp[i]代表以第i个元素结尾的升序序列长度为多少):
dp[i] = dp[j]+1(如果存在i前面的一个j,满足src[j] < src[i] 且 dp[j] > dp[i]-1)
dp[i] = dp[i] (如果前面存在一个j,满足src[j] < src[i] 但是 dp[j] <= dp[i]-1)
dp[i] = 1 (如果前面所有的元素都大于第i个元素,以及初始化数据)

最后由于我们并不知道真正的最长子序列是以哪个元素结尾的,所以我们要找出dp数组中的最大值。

好了,思路大致如上,下面给出代码:

//program: finding the longest monotone increasing subsequence
//author: William.L lbb@hnu.edu.cn
//version: v1.0

#include <iostream>
using namespace std;

int main(){
    int len;
    cin >> len;
    int src[len];
    for (int i = 0;i < len; i++){
        cin >> src[i];
    }
    int endw[len] = {1};
    for (int i = 0;i < len; i++){
        for (int j = 0; j < i; j++){
            if ((src[j] < src[i]) && (endw[j] + 1 > endw[i])){
                endw[i] = endw[j] + 1;
            }
        }
    }
    int maxlen = 1;
    for (int i = 0;i < len; i++){
        if (endw[i] > maxlen){
            maxlen = endw[i];
        }
    }
    cout << maxlen << endl;
    return 0;
}

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值