《編程之美》里有個題目是要求數組中最長遞增子序列,在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。下面是代碼:
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跟這些最小值比,當然我們可以用折半查找,代碼如下:
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