目录
最长上升子序列:
一个数的序列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];