这个题虽然是很早就做了,但是当初优化到O(nlogn),一直没能理解。
dp[i]就表示以nums[i]结尾的最长上升子序列的长度。
转移方程:dp[i] = max{ dp[j] + 1} (0 <= j < i )
时间复杂度:O(n^2)
class Solution {
public int lengthOfLIS(int[] nums) {
int[] dp = new int[nums.length];
Arrays.fill(dp, 1);
for (int i = 0; i < dp.length; i++) {
int max = 0;
for(int j = 0; j < i; j++) {
if(nums[i]>nums[j])
max = Math.max(dp[j], max);
}
dp[i] = max+1;
}
int max = 0;
for (int i = 0; i < dp.length; i++) {
//System.out.print(dp[i]+" ");
if(dp[i] > max)
max = dp[i];
}
return max;
}
}
想法:外层遍历是必须的,但内层循环目的就是为了找一个最大的dp[j](0 <= j < i),不由自主的就想要是dp是一个有序的,用二分查找,把时间复杂度降到O(logn)多好,促使我们想一个新的状态表示方法。
优化:
将dp[i]描述为“长度为i的最长上升子序列的最小结尾是多少”
d数组中的元素是严格递增的。遍历nums序列中的一个元素nums[i]时,都有两种情况:
- nums[i] > dp[length],遍历到的元素比 当前找到的最长的上升子序列的最小结尾元素要大,那么我们就找到了一个更长的上升子序列,并且结尾元素是当前遍历到的元素, 进行操作:
dp[++length] = nums[i];
- nums[i] <= dp[length],用折半查找更新dp数组中的某个元素。
(如果要找的是非递减的上升子序列,那么第一种判断条件应该是>=,第二个<)
两种情况,第一种时间复杂度为O(1),第二种由于采用了折半查找,时间复杂度为O(logn)
总时间复杂度O(nlogn)
最坏情况下的,空间复杂度O(n),代码中没有用list,开了一个dp数组,所以空间复杂度一直是最坏的空间复杂度O(n)
class Solution {
int[] dp;
public int lengthOfLIS(int[] nums) {
int n = nums.length;
if (n == 0)
return 0;
dp = new int[n+1];
int length = 1;
dp[1] = nums[0];
for (int i = 1; i < n; i++) {
//遍历到的元素比当前最长上升子序列的最小结尾的数还要大
if (nums[i] > dp[length])
dp[++length] = nums[i]; //最长序列要+1,以当前元素结尾
//
else {
//找到需要更新的dp下标
int temp = binary_search(nums, 1, length, nums[i]);
dp[temp] = nums[i];
}
}
return length;
}
//折半查找,找不到就返回最小的比nums[i]大的元素
private int binary_search(int[] nums, int left, int right, int target) {
while (left < right) {
int mid = left + (right - left) / 2;
if (dp[mid] >= target)
right = mid;
else
left = mid + 1;
}
return left;
}
}