题目描述
求一个一维数组arr[i]中的最长递增子序列的长度,如在序列1,-1,2,-3,4,-5,6,-7中,最长递增子序列长度为4,可以是1,2,4,6,也可以是-1,2,4,6。
题目分析
最长递增子序列(Longest Increasing Subsequence)又叫做最长上升子序列;子序列,正如LCS一样,元素不一定要求连续。
方法一:DP(O(n2))
像LCS一样,从后向前分析,很容易想到,第i个元素之前的最长递增子序列的长度要么是1(单独成一个序列),要么就是第i-1个元素之前的最长递增子序列加1,可以有状态方程:
LIS[i] = max{1, LIS[k]+1}
其中,对于任意的k<=i-1,arr[i] > arr[k],这样arr[i]才能在arr[k]的基础上构成一个新的递增子序列。
#include <iostream> using namespace std; int LIS(int * arr, int size) { int dp[31]; for (int i = 0; i < size; ++i) { dp[i] = 1; for (int j = 0; j < i; ++j) { if (arr[i] > arr[j] && dp[i] < dp[j] + 1) { dp[i] = dp[j] + 1; } } } int result = 0; for (int i = 0; i < size; i++) { if (dp[i] > result) { result = dp[i]; } } return result; } int main() { // int arr[] = { 1, -1, 2, -3, 4, -5, 6, -7 }; // int arr[] = { 1, 4, 6, 3, 2 }; int arr[] = { 5, 2, 1, 4, 5, 4, 5, 3, 6 }; int len = LIS(arr, sizeof(arr) / sizeof(int)); cout << len << endl; getchar(); return 0; }
方法二:DP+二分查找(O(nlogn))
《编程之美》对于这个方法有提到,不过它的讲解我看得比较难受,好长时间才明白,涉及到的数组也比较多,除了源数据数组,有LIS[i]和MaxV[LIS[i]],后来看了大牛Felix的讲解,我才忽然发现编程之美中的这个数组MaxV[LIS[i]]在记录信息上其实是饶了弯的,因为我们在寻找某一长度子序列所对应的最大元素最小值时,完全没必要通过LIS[i]去定位,即没必要与数据arr[i]挂钩,直接将MaxV[i]的下标作为LIS的长度,来记录最小值就可以了(表达能力太次,囧。。。),一句话,就是不需要LIS[i]这个数组了,只用MaxV[i]即可达到效果,而且原理容易理解,代码表达也比较直观、简单。
下面说说原理:
目的:我们期望在前i个元素中的所有长度为len的递增子序列中找到这样一个序列,它的最大元素比arr[i+1]小,而且长度要尽量的长,如此,我们只需记录len长度的递增子序列中最大元素的最小值就能使得将来的递增子序列尽量地长。
方法:维护一个数组MaxV[i],记录长度为i的递增子序列中最大元素的最小值,并对于数组中的每个元素考察其是哪个子序列的最大元素,二分更新MaxV数组,最终i的值便是最长递增子序列的长度。这个方法真是太巧妙了,妙不可言。
#include <iostream> using namespace std; /* 返回MaxV[i]中最左边的大于等于x的那个元素的下标 */ int BinSearch(int * MaxV, int size, int x) { int left = 0, right = size - 1; while (left <= right) { int mid = (left + right) / 2; if (MaxV[mid] < x) { left = mid + 1; } else { right = mid - 1; } } cout << x << ":" << MaxV[left] << endl; return left; } int LIS(int * arr, int size) { int MaxV[30]; /* 存储长度i+1(len)的子序列最大元素的最小值 */ MaxV[0] = arr[0]; /* 初始化 */ int len = 1; /* 存储子序列的最大长度 即MaxV当前的下标*/ for (int i = 1; i < size; ++i) /* 寻找arr[i]属于哪个长度LIS的最大元素 */ { if (arr[i] > MaxV[len - 1]) /* 大于最大的自然无需查找,否则二分查其位置 */ { MaxV[len++] = arr[i]; } else { int pos = BinSearch(MaxV, len, arr[i]); MaxV[pos] = arr[i]; } } return len; } int main() { // int arr[] = { 1, -1, 2, -3, 4, -5, 6, -7 }; // int arr[] = { 1, 4, 6, 3, 2 }; int arr[] = { 5, 2, 1, 4, 5, 4, 5, 3, 6 }; // 4 int len = LIS(arr, sizeof(arr) / sizeof(int)); cout << len << endl; getchar(); return 0; }