[编程之美] PSet2.16 求数组中最长的递增子序列

来源:互联网 发布:萧山网络问政 高桥 编辑:程序博客网 时间:2024/06/05 18:09


 问题描述:

    写一个时间复杂度尽可能低的程序,求一个一维数组中最长递增子序列的长度。

    例如在序列1,-1,2,-3,4,-5,6,-7中,其最长的递增子序列的长度为4(如1,2,4,6)。


解答与思路:

       这个题目与前面求一维数组中子数组之和最大值有点像,不过区别还是很明显,比如:子数组是数组中一串连续相邻的数字,而子序列不一定是相邻的,因此要得到[0-k]的子数组最大和,只要分析[0-(k-1)]的子数组最大和即可,而考虑[0-k]的子序列的最长长度,就不能只分析[0-(k-1)]中子序列的最长长度。

       解法一:暴力枚举,对于每一个子序列,都判断是否能构成递增的,然后选取其中最大的。子序列有2^N种(对原数组的每个数,可以选择加入子序列也可以不选择),这样的算法复杂度O(2^N)。

       解法二:暴力枚举,对于每一个个数组中的元素,判定后面某元素是否有元素大于目前元素,并寻找当前元素为首的对应元素最长子序列。该算法复杂度O(N^2),代码:

//方法二:对每个确定的i扫描一遍数组,寻找当前最长递增子序列长度。int findLIS(int Arr[] , int arrLen){int LIS = 0;for(int i=0 ; i<arrLen ; i++){int temp = Arr[i];int count = 0;for(int j=1 ; j<arrLen ; j++){if(Arr[j] > temp){temp = Arr[j];count++;}}if(count > LIS)LIS = count;}return LIS;}

       解法三:动态规划法,假设目标数组的前i个元素中,最长递增子序列长度为LIS[i],考虑对于任意的arr[i+1]>arr[k],将会导致LIS[i+1]=max(1,LIS[k]+1),因为第i+1个元素本身可以直接接在LIS[k]长的子序列后面构成一个更长的子序列,于此同时arr[i+1]本身至少可以构成一个长度为1的子序列(用于下次增加长度之前的初始化)。这种方法空间复杂度O(N),时间复杂度O(N^2),代码:

//方法三:动态规划法//对于LIS[i],有如果arr[i+1]>arr[k],则LIS[i+1] = max{1,LIS[k]+1}//因为LIS下标从0开始,定义LIS[i]为前i+1个元素的最长递增子序列长度int findLIS(int Arr[] , int arrLen){int *LIS = new int[arrLen];for(int i=0 ; i<arrLen ; i++){LIS[i] = 1;for(int k=0 ; k<i ; k++){if(Arr[i]>Arr[k] && LIS[i]<LIS[k]+1){//寻找最大的LIS[k]+1赋值给LIS[i]LIS[i] = LIS[k]+1;}}}//---寻找LIS中最大值int maxLIS = -9999;for(int j=0 ; j<arrLen ; j++){if(maxLIS < LIS[j])maxLIS = LIS[j];}delete []LIS;LIS = NULL;return maxLIS;}

       解法四:维护不同长度的递增子序列的最大元素最小值MaxV[LIS[i]],可以考虑找到前i个元素中的一个递增子序列,使得这个递增子序列的最大元素比arr[i+1]小,且长度尽可能长。由于else的部分需要逆序遍历,因此时间复杂度还是O(N^2),不过比上面的方法会快一些。代码:

//方法四:考虑前面i个元素的分布情况//定义MaxV[i]为长度为i的递增子序列最大元素的最小值,int findLIS(int Arr[] , int arrLen){int *MaxV = new int[arrLen+1];// 递增子序列最长不超过arrLenint maxLen = 1;MaxV[0] = INT_MIN;MaxV[1] = Arr[0];for(int i=1 ; i<arrLen ; i++){//从Arr[1]遍历到Arr[N-1]if(Arr[i] > MaxV[maxLen]){maxLen++;MaxV[maxLen] = Arr[i];}else{//否则逆序寻找能否有MaxV[j]<Arr[i]<MaxV[j+1],此时可以更新MaxV[j+1]=Arr[i]int j=maxLen-1;while(Arr[i]<MaxV[j])//MaxV[0]作为哨兵,为-INFj--;//跳出循环时Arr[i]>MaxV[j],当前子数组最小的数更新MaxV[1]的值。MaxV[j+1] = Arr[i];}}delete []MaxV;MaxV = NULL;return maxLen;}
        解法四改进:由于是递增序列,因此对于i<j一定有MaxV[i]<MaxV[j]。依据单调递增的关系,考虑使用二分搜索进行加速,将时间复杂度降低为O(NlogN)。代码如下:

//方法四改进:将穷举逆序搜索MaxV[j]<Arr[i]<MaxV[j+1]改为二分搜索//复杂度提高为O(NlogN)int findLIS(int Arr[] , int arrLen){int *MaxV = new int[arrLen+1];// 递增子序列最长不超过arrLenint maxLen = 1;MaxV[0] = INT_MIN;MaxV[1] = Arr[0];for(int i=1 ; i<arrLen ; i++){//从Arr[1]遍历到Arr[N-1]if(Arr[i] > MaxV[maxLen]){maxLen++;MaxV[maxLen] = Arr[i];}else{//二分搜索MaxV[j]<Arr[i]<MaxV[j+1],注意MaxV[0]可以更新MaxV[1]为合理的更小值int b = 0;int e = maxLen-1;while(b<e){int mid = b+((e-b)>>1);//防止上溢if(MaxV[mid] < Arr[i])b=mid+1;elsee=mid-1;}//跳出循环说明找到这样的地方或未能找到if(MaxV[b] < Arr[i])MaxV[b+1] = Arr[i];}}delete []MaxV;MaxV = NULL;return maxLen;}int main(void)  {   int a[8] = {1, -5, 2, -4, 4, -3, -2, -1}; cout<<findLIS(a , sizeof(a)/sizeof(int))<<endl;return 0;  }  


0 0
原创粉丝点击