300. 最长上升子序列(LIS的优化)

300. 最长上升子序列

这个题虽然是很早就做了,但是当初优化到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]时,都有两种情况:

  1. nums[i] > dp[length],遍历到的元素比 当前找到的最长的上升子序列的最小结尾元素要大,那么我们就找到了一个更长的上升子序列,并且结尾元素是当前遍历到的元素, 进行操作:dp[++length] = nums[i];
  2. 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;
	}
}
发布了56 篇原创文章 · 获赞 4 · 访问量 1667

猜你喜欢

转载自blog.csdn.net/qq_41342326/article/details/104359595