最長遞增/遞減子序列


《編程之美》里有個題目是要求數組中最長遞增子序列,在CSDN上看到的題目是數組中的最長遞減子序列。題目如下:

求一個數組的最長遞減子序列

比如{9,4,3,2,5,4,3,2}的最長遞減子序列為{9,5,4,3,2}

求一個數組的最長遞增子序列

比如{1,-1,2,-3,4,-5,6,-7}的最長遞減子序列為{1,2,4,3,6}

最長遞增序列和最長遞減子序列的解法是一樣的,最不濟,也可以先revert,求完再revert一次。

首先我們得搞清一個范圍問題,雖然我們要的是一個最大值,但是是不是他是可以遞推的呢,就是我只保存一個最大值,然后不斷增加這個值,直覺上看應該是不太現實的,比如:12345-101234,我們數到5時最大值是5,但往后看時,都比5小,不能增加最大值,但事實上是最大值是6,如果我們受限於前面求的的一個最大值,就會產生錯誤的結果,也許你可以說,我就是保存一個值,沒到一個點我就重新算新的最大值,雖然犧牲一點計算,但是我就想只有一個變量,有思路嗎?如果沒有就換個思路吧。既然遞推困難,我們就把各個備選值都列出來,然后找出那個最大的,那哪些是備選值呢?其實以任意元素為終點的遞增序列都是合法的candidate。

解法1:

如同上面的分析,考慮一個序列x0, x1,x2,… xn-1, xn。我們想知道以每個元素為終點的最長遞增子序列的長度,令temp[i]表示以xi為終點的遞增序列的長度,假設已經知道了temp[0]-temp[n-1],那如何求temp[n]呢?如果xi<xn那,xi就有可能在xn為終點的序列上,那以xn為終點的遞增序列就至少是xi的最長序列+1。下面是代碼:

 

#include <iostream>
using  namespace std;

int LIS( int arr[], int n)
{
     int *temp =  new  int[n]; // 存放當前遍歷位置最長序列 
     for( int i= 0;i<n;++i)
    {
        temp[i]= 1;    // 初始化默認長度 
         for( int j= 0;j<i;++j)  // 找出前面最長的序列 
        {
             //  當前值 array[i] 跟已經遍歷的值比較,
            
// 大於已經遍歷的值且已知遞增序列+1 大於當前值則 更新當前最長遞增序列值 
             if(arr[i]>arr[j]  && temp[j]+ 1 > temp[i] )
            {
                temp[i] = temp[j] +  1;
            }
            
        }
    }
    
     int max=temp[ 0];
     for( int k= 0;k<n;++k) // 找出整個數組中最長的子序列 
    {
         if(max<temp[k])
            max=temp[k];
    }
    
     return max;
    
}

int main()
{
     int arr[]={ 1,- 1, 2,- 3, 4,- 5, 6,- 7};
     int result=LIS(arr, 8);
    cout<<result<<endl;
    
}

 

解法2:

解法1中用一個數組存儲以各個元素為終點的最長遞增子序列的長度,然后每新增加一個元素時,就遍歷前面所有的元素,來知道新元素的temp值,復雜度是O(n2)。每個元素都得求一遍,這是毋庸置疑的,求法上能不能優化一下呢?那就把前面元素和其最長遞增序列值排個序吧,當求xn的最長遞增序列時,找到前面的序列中比xn小的最大值(可能有多個,位置不同時,序列長度也會不一樣),然后加一個1就可以了。代碼如下:

 

解法3:

相類似於解法2,我們不是把序列排序,而是按照序列長度排序,並記錄特定長度序列下終點元素的最小值,然后用xn跟這些最小值比,當然我們可以用折半查找,代碼如下:

 

#include <iostream>
using  namespace std;

int LIS( int array[], int n)
{
     int temp[n]; // 存放當前遍歷位置最長序列 
     int  MaxV[n];  // 最長子序列中最大值之間的最小值
    MaxV[ 1]=array[ 0]; // 初始序列長度為1的子序列 中最大值的最小值
    MaxV[ 0]=- 9999; // 邊界值
     
      for( int i= 0;i<n;++i)
    {
        temp[i]= 1;    // 初始化 默認長度 
    } 
      int  nMaxLis= 1;
      int  j; 
     for( int i= 0;i<n;++i)
    {
         for(j=nMaxLis;j>= 0;--j)  // 找出前面最長的序列 
        {
             if(array[i]>MaxV[j]) // 當前值大於長度為j的子序列中最大值之間的最小值 
            {
                temp[i] = j +  1;
                 break
            }
            
        }
        
         if(temp[i]>nMaxLis) // 在最長子序列時停止 (這時只有一個最長的) 
        {
            cout<< " nMaxLIs "<<nMaxLis<<endl; 
            
            nMaxLis=temp[i];
            MaxV[temp[i]]=array[i]; 
        } 
         else  if(MaxV[j] <array[i] && array[i]<MaxV[j+ 1])
        {
            MaxV[j+ 1]=array[i]; 
        } 
    }
    
    
     return nMaxLis;
    
}

int main()
{
     int arr[]={ 1,- 1, 2,- 3, 4,- 5, 6,- 7};
     int result=LIS(arr, 8);
    cout<<result<<endl;
    
}

 

解法4:

還記得我們關於序列的一個經典問題:最長公共子序列。是不是可以轉化呢?將原序列排序,並和原序列求最長公共子序列,求得的最長公共子序列長度就是最長遞增子序列的長度。

 

Reference

1. http://blog.csdn.net/tianshuai11/article/details/7887810


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM