算法实验-最长单调递增子序列长度

问题描述

解决思路

O(n^2)

        首先考虑使用动态规划的方法解决该问题。首先将原问题分解为子问题。对于长度为n的序列(从下标1开始),假设前n-1个元素形成了n-1个以arr[i]结尾的单调递增最长子序列,对于每个单调递增最长子序列,如果arr[n]>arr[i],第n个元素就可以添加到这个子序列的末尾,这个子序列的长度加一,n个元素的最大的单调递增子序列长度,就是更新后的前n-1个单调递增最长子序列中的最大长度。该问题具有最优子结构性质。根据最优子结构性质,可以得出递推公式:

if(arr[i] > arr[j]) dp[i] = max(dp[i], dp[j] + 1)  1≤j<i

        求以arr[i]结尾的单调递增最长子序列,需要遍历arr[1]到arr[i-1]结尾的单调递增最长子序列的长度,而得到整个序列的单调递增最长子序列,需要求出以每个元素结尾的单调递增最长子序列的长度,结果为其中的最大值。因此该方法的时间复杂度上界为O(n^2)。

O(nlogn)

        可以采用更好的方法解决单调递增最长子序列问题。在以上的解决方法中,计算dp[i]时,每次都要寻找到arr[i-1]为止的每个元素结尾的单调递增最长子序列的最大值,实际上这些可能产生最优解的子序列中,有一些是可以排除掉的,例如假设对于arr[x]和arr[y],它们的单调递增最长子序列分别为1,3,7和1,3,15,长度均为3,如果在这两个序列后面添加一些元素使其更长,一定是在1,3,7后面添加元素比在1,3,15后面添加元素得到的子序列更长,那么1,3,15这个序列就不需要在后续的更新中考虑了。从上面这个例子可以看出,只需要保留每个长度的单调递增子序列的最后一个元素,然后进行更新,寻找可能的更大长度的递增子序列。

        可以使用一个tail数组保存尾元素,tail[i]表示长度为i的单调递增子序列的尾元素。从第一个元素开始更新时,tail[1]就是第一个元素的值,如果遇到了一个比tail[1]大的元素x,那么说明此时单调递增子序列的长度可以扩展为2,就令tail[2]=x,如果再遇到一个比tail[2]大的元素,说明单调递增子序列的长度可以扩展为3,按照这个规则去更新,显然tail会是一个单调递增的序列。如果遇到一个元素,不比当前的最大长度的末尾元素大,那么长度不可以拓展,但是这个元素可以更新某个特定长度i的tail[i],这个元素比tail[i]小,比tail[i-1]大,表示它可以补在长度i-1的子序列后面,又比现有的长度为i的子序列尾元素小。

        通过逐个元素进行对tail的更新,已知最大长度的单调递增子序列的尾元素就可能变小,再遇到后面的元素时,就可能产生新的更长的单调递增子序列。在tail这个单调递增的数组中,可以使用二分法查找这个位置i,这样就降低了整体的时间复杂度到O(nlogn)。

代码实现

int LIS(int arr[], int len){
    int tail[len+1];
    int curMaxLen, updatePos;
    curMaxLen = 0;
    tail[0] = INT_MIN;
    for(int i=1;i<=len;i++){
        if(arr[i]>tail[curMaxLen]){
            tail[++curMaxLen] = arr[i];
        }
        else{
            updatePos = binarySearch(tail, 0, curMaxLen, arr[i]);
            tail[updatePos] = arr[i];
        }
    }
    return curMaxLen;
}
//二分查找
int binarySearch(int arr[], int left, int right, int x){
    int mid;
    while(left<=right){
        mid = (left + right) / 2;
        if(arr[mid]>x){
            right = mid-1;
        }
        else{
            left = mid+1;
        }
    }
    return left;
}

  • 0
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值