最长递增子序列长度LIS
最长递增子序列Longest Increasing Subsequence (LIS) 指一个序列中所包含的最长的递增子序列。
例如, 序列{2, 1, 4, 2, 3, 7, 4, 6}的LIS是{1, 2, 3, 4, 6},其长度为5。写一函数计算一个序列中的最长子序列长度。
一般思路:使用动态规划,数组记为a[],用f[i]表示从第0个元素到第i个元素的LIS长度(初始化为1,因为a[i]本身长度为1),对于每一个i,需要和i以前的元素进行比较,并更新f[i]的值,即比较a[i]和a[j],0 <= j < i。
状态转移方程为f[i] = max{f[i], f[j]+1, if a[i] > a[j]}, 0 <= j < i。 用result记录最长的LIS长度。时间复杂度为O(N^2)。
code:
1 //最长递增子序列长度Version1, DP 2 int LongestIncreasingSubsequenceV1(int *a, int N) 3 { 4 if (NULL == a || N <=0) 5 { 6 return -1; 7 } 8 int *f = new int[N]; 9 //初始化f[i] 10 for (int i = 0; i < N; i++) 11 { 12 f[i] = 1; 13 } 14 15 int result = f[0]; 16 17 for (int i = 1; i < N; i++) 18 { 19 for (int j = 0; j < i; j++) 20 { 21 if (a[i] > a[j] && f[j] + 1 > f[i]) 22 { 23 f[i] = f[j] + 1; 24 } 25 } 26 if (result < f[i]) 27 { 28 result = f[i]; 29 } 30 } 31 delete f; 32 f = NULL; 33 return result; 34 }
改进:上面比较a[i]和a[j]是通过遍历来比较的,有没更好的方法?
假如使用B[len]来记录长度为len的LIS的最小末尾,那么就可以直接比较a[i]和B[len],找到a[i]在B中合适的位置。
如序列a[8]={2, 1, 4, 2, 3, 7, 4, 6},初始B[0] = (signed int)0x80000000,B[1] = a[0] =2;
对于a[1],a[1]<B[1],更新B[1] = 1;
对于a[2],a[2]>B[1],故B[2] = 4;
对于a[3],B[1]<a[3]<B[2],更新B[2] = 2;
对于a[4] ,a[4]>B[2],故B[3] = 3;
对于a[5] ,a[5]>B[3],故B[4] = 7;
对于a[6],B[3]<a[6]<B[4],故更新B[4]=4;
对于a[7],a[7]>B[4],故B[5]=6.至此过程结束。
从上面过程可以发现,对于长度为i的最小末尾数组B[i],有以下性质,当i<j时, B[i]<B[j]。在有序数组中查找,可以使用二分查找提高查找效率。
设当前总长度为k时,比较a[i]和B[k]
1.若a[i]>B[k],则B[k+1] = a[i]
2.若a[i]<B[k], 二分查找B中位置j使得,B[j]<a[i]<B[j+1],并更新B[j+1] = a[i];
这两种情况可以合并在一起。
code:
1 //最长递增子序列长度Version2, 改进的DP 2 int LongestIncreasingSubsequenceV2(int *a, int N) 3 { 4 if (NULL == a || N <=0) 5 { 6 return -1; 7 } 8 9 int *B = new int[N + 1]; 10 B[0] = (signed int)0x80000000; 11 B[1] = a[0]; 12 int len = 1; 13 int low; 14 int high; 15 int mid; 16 for (int i = 1; i < N; i++) 17 { 18 low = 0; 19 high = len; 20 while (low <= high) 21 { 22 mid = low + (high - low) / 2; 23 if (a[i] > B[mid]) 24 { 25 low = mid + 1; 26 } 27 else 28 { 29 high = mid - 1; 30 } 31 } 32 B[low] = a[i]; 33 if (low > len) 34 { 35 len++; 36 } 37 } 38 delete B; 39 B = NULL; 40 return len; 41 }