【经典动态规划问题】最长上升子序列 LIS

目录

 

最长上升子序列:

O(N^2)动态规划:

O(N*logN):贪心+二分


最长上升子序列:

一个数的序列bi,当b1 < b2 < … < bS的时候,我们称这个序列是上升的。对于给定的一个序列(a1, a2, …, aN),我们可以得到一些上升的子序列(ai1, ai2, …, aiK),这里1 <= i1 < i2 < … < iK <= N。

O(N^2)动态规划:

状态:dp[i],表示以A[i]为结尾数组的的最长上升子序列长度

初始化:A[i]的最长上升子序列最小为1,也就是左边的数都大于等于A[i],最长上升子序列只有A[i]元素本身,dp[i] = 1;

dp[i]表示以A[i]的最长子序列长度,我们现在找倒数第二个元素。

遍历A[0,...i-1],设其中小于A[i]的元素为A[k,j,z...],倒数第二个元素必定在A[k,j,z]中,dp[i]有几种可能:

 dp[i] = dp[k]+1;

dp[i ]= dp[j]+1;

dp[i] = dp[z]+1;

取这几种可能结果中的最大值,即为dp[i]的值。

最后返回的则是dp数组中的元素最大值。

class LongestIncreasingSubsequence {
public:
    int getLIS(vector<int> A, int n) {
        // write code here
        //dp[i]为A[0...i]中的最长上升子序列的长度
        int dp[501]= {0};
        for(int i = 0;i<n;i++){
            dp[i] = 1;//初始化最长上升子序列的长度为1,即自身
        }
        int ans = 1;//记录最大值
        for(int i = 1;i<n;i++){
            for(int j = 0;j<i;j++){
            if(A[j]<A[i])
                dp[i] = max(dp[i],dp[j]+1);
            }
            //重要的是最大值
            ans = max(dp[i],ans);
        }
       return ans;
    }
};

这道题还有复杂度更低的做法, 可在面试中作为算法的优化,同时能够返回最长上升子序列的元素值。

O(N*logN):贪心+二分

从左到右遍历数组,用dp数组维护当前的最长上升子序列,size变量维护最长上升子序列的长度。

如A={ 2,1,3,4,8,6}

A[0]等于2,dp = {2},size=1;

A [1]等于1,小于dp的末尾元素2,因此让1把2替换掉,dp = {1},size = 1;长度为1的最长子序列,结尾元素为1显然比结尾元素为2的序列更容易加"上升元素"

A [2]等于3,大于dp的末尾元素1,因此dp中加入2,dp = {1,2},size = 2;

A [3]等于4,大于dp的末尾元素2,因此dp中加入4,dp = {1,2,4},size = 3;

A [4]等于8,大于dp的末尾元素4,因此dp中加入8,dp = {1,2,4,8},size = 4;

A [5]等于6,小于dp的末尾元素8,大于倒数第二个元素4,因此将6替换8,dp = {1,2,4,6},size = 4,因为长度为4的最长上升子序列,结尾是6会比结尾是8的序列要更有利于添加新元素。

得到最长上升子序列为{1,2,4,6},长度为4;

这里使用了贪心策略:一个上升子序列,最后一个元素越小,越有利于添加后续比它大的元素。

dp为有序数组,因此若A[i]小于dp末尾元素,需要找到dp中第一个大于等于A[i]的元素并让A[i]替换掉它。(较小的值越往前面约好)

注意这里的二分查找是求下界的,即>=所查找对象的第一个位置。

class LongestIncreasingSubsequence {
public:
    int binarysearch(int A[], int n,int item){
        int start = 0;
        int end = n-1;
        while(start<=end){
            int mid = (start+end)/2;
            if(A[mid]<item)
                start =  mid+1;
            else
                end = mid-1;
        }
        return start;
    }
    int getLIS(vector<int> A, int n) {
        // write code here
        //dp[i]为A[0...i]中的最长上升子序列的长度
        int dp[501]= {0};
        int size = 0;
         dp[size++] = A[0];
        for(int i = 1;i<n;i++){
           if(A[i]> dp[size-1])//当前元素大于dp数组中的最后一个元素
               dp[size++] = A[i];
            else{
                 int firstBig =  binarysearch(dp,size,A[i]);//用二分法找到第一个大于等于A[i]的值,进行替换
                 dp[firstBig] =  A[i];
            }
         
        }
        return size;
    }
};

此外在求数组中第一个大于等于A[i]的值时,可以使用lower_bound()

lower_bound(int* first,int* last,val);函数在(first,last](左开右闭)区间中进行二分查找。因此数组必须是排好序的数组。

函数返回从first开始的第一个大于或等于val的元素的地址。如果所有元素都小于val,则返回last的地址。

第一个大于等于A[i]的数组下标为:pos =   lower_bound(dp,dp+size,A[i])-dp

因此  int firstBig = dp[pos];

猜你喜欢

转载自blog.csdn.net/gulaixiangjuejue/article/details/85243222
今日推荐