(I
这题CLRS有,O(n^2)的解是比较典型的DP思路。DP的递归式如下
f(i) = max(1, max(f(q1), ... f(qn)) + 1); (其中q1..qn是nums[qn] < nums[i]的集合) 。根据这个递归式我们就可以知道代码如下:
public int lengthOfLIS(int[] nums) {
int[] dp = new int[nums.length];
int result = 0;
for (int i = 0; i < nums.length; i++) {
dp[i] = 1;
for (int j = 0; j < i; j++) {
if (nums[j] < nums[i]) {
dp[i] = Math.max(dp[j] + 1, dp[i]);
}
}
result = Math.max(dp[i], result);
}
return result;
}
至于nlogn的解法。就不太接近正常DP的思想了。做法基于二分查找以及DP。
和DP一样,你需要一个缓存数组,数组里面放的东西,唔,难以描述。这个缓存数组是递增的但它并不是输入数组的LIS。因为它在不停尝试着构造可能是LIS的东西。这是一个虽然不是LIS但是能够构造LIS长度的一个东西。
举个例子说一下比较清楚,
譬如说1,24,3,53,20,22,24,13,42,首先肉眼可见,LIS是1,3,20,22,24,42,长度为6。
缓存数组的构建过程是这样的:
1 - 1
24 - 1, 24
3 - 1, 3
53 - 1, 3, 53
20 - 1, 3, 20
22 - 1, 3, 20, 22
24 - 1, 3, 20, 22, 24
13 - 1, 3, 13, 22, 24
42 - 1, 3, 13, 22, 24, 42
最后缓存数组的结果和实际的LIS是有出入的。所以说这个缓存数组是在不停的尝试更新一个类似LIS的东西。它在某一步可能确实是LIS,但是整体来说并不是。它就根据LIS的规矩来维护这个缓存数组,如果新进来的数字比缓存任何数字都要大,那么缓存数组就往后加一个,如果它比中间任何一个要小,那就更新刚好比它大的数字,所以这个时候就是二分需要的时候了。所以,最后最后是NLOGN。
下面给出对应的代码:
public int lengthOfLIS(int[] nums) {
int[] dp = new int[nums.length];
int result = -1;
for (int i = 0; i < nums.length; i++) {
if (result == -1 || dp[result] < nums[i]) {
result++;
dp[result] = nums[i];
} else {
int index = this._binarySearch(dp, 0, result, nums[i]);
dp[index] = nums[i];
result = Math.max(result, index);
}
}
return result + 1;
}
private int _binarySearch(int[] nums, int head, int tail, int target) {
while (head <= tail) {
int mid = (head + tail) / 2;
if (nums[mid] == target) {
return mid;
} else if (nums[mid] < target) {
head = mid + 1;
} else {
tail = mid - 1;
}
}
return head;
}
if (result == -1 || dp[result] < nums[i])这一段是一个小的optimization,让代码从1ms变成了0ms。。其实可以不要的。。囧rz..