问题定义:
给定一个长度为N的数组A,找出一个最长的单调递增子序列(不要求连续)。
这道题共3种解法。
1. 动态规划
动态规划的核心是状态的定义和状态转移方程。定义lis(i),表示前i个数中以A[i]结尾的最长递增子序列的长度。可以得到以下的状态转移方程:
d(i) = max(1, d(j) + 1), 其中j < i,且A[j] <= A[i]
程序实现:
int longestIncreasingSubsequence(vector<int> nums) { if (nums.empty()) return 0; int len = nums.size(); vector<int> lis(len, 1); for (int i = 1; i < len; ++i) { for (int j = 0; j < i; ++j) { if (nums[j] < nums[i] && lis[i] < lis[j] + 1) lis[i] = lis[j] + 1; } } return *max_element(lis.begin(), lis.end()); }
程序复杂度为O(N^2)
2. 动态规划 + 二分查找
换一种角度看问题。令Ai,j表示所有长度为j的最大递增子序列的最小末尾,我们有Ai,1 < Ai,2 < ... < Ai,j。
对A[i+1]来说,有两种选择。
1. Ai,j < A[i+1], 此时我们可以得到一个长度为i+1的最大递增子序列。
2. 替换Ai,k,如果Ai,k-1 < A[i+1] < Ai,k。
替换长度为k的最大递增子序列的最小末尾,是为了增大获取更长子序列的机会。
程序实现:
int binarySearch(const int arr[], int low, int high, int val) { while (low <= high) { int mid = low + (high - low) / 2; // Do not use (low + high) / 2 which might encounter overflow issue if (val < arr[mid]) high = mid - 1; else if (val > arr[mid]) low = mid + 1; else return mid; } return low; } int LIS(int arr[], int n) { int *minTail = new int[n]; minTail[0] = arr[0]; int len = 1; for (int i = 1; i < n; ++i) { if (arr[i] > minTail[len-1]) minTail[len++] = arr[i]; else { int pos = binarySearch(minTail, 0, len-1, arr[i]); minTail[pos] = arr[i]; } } delete [] minTail; return len; }
复杂度:O(nlogn)
reference :
最长递增子序列(LIS)
3. 排序+LCS
这种方法就不细说了。。。