DP系列-leetcode 300. Longest Increasing Subsequence(最长递增子序列)

Given an unsorted array of integers, find the length of longest increasing subsequence.

Example:

Input: [10,9,2,5,3,7,101,18]
Output: 4 
Explanation: The longest increasing subsequence is [2,3,7,101], therefore the length is 4. 

Note:

  • There may be more than one LIS combination, it is only necessary for you to return the length.
  • Your algorithm should run in O(n2) complexity.

Follow up: Could you improve it to O(n log n) time complexity?

 

思路1:DP(时间复杂度o(n^2))

Dp[i] 表示以第i个数字为结尾的最长上升子序列的长度。
对于每个数字,枚举前面所有小于自己的数字 j,Dp[i] = max{Dp[j]} + 1. 如果没有比自己小的,Dp[i] = 1;

实现:

class Solution {
    public int lengthOfLIS(int[] nums) {
        int[] dp=new int[nums.length];
        int max=0;
        for(int i=0;i<dp.length;i++){
            dp[i]=1;
            for(int j=0;j<i;j++){
                if(nums[j]<nums[i]){
                    dp[i]=Math.max(dp[i],dp[j]+1);
                }
            }
             if(dp[i]>max){
                 max=dp[i];
             }
        }
        return max;
    }
}

 

思路2:二分查找(时间复杂度o(nlogn)

方法一递推关系简单, 代码实现也异常简洁, 唯一的问题是n ^ 2的复杂度在题目给的数据量较大时会超时。

而方法一也不算是最优的解决方法。实际上这个问题还可以用二分来优化。

做法是构造出一个新的有序的DP数列, 用原数列中的数从左到右维护更新新数列。

初始时DP[1] = nums[1], 从i = 2时遍历原数列, 将每个遍历的数与DP数列的末尾进行比较, 如果大于末尾, 则把DP数列长度提1将nums[i]放在DP数列的最后, 如果小于末尾那么替换掉DP数列中比s[i]大的第一个数。

结束后DP数列的长度就是LIS的长度。

从LIS的性质出发,要想得到一个更长的上升序列,该序列前面的数必须尽量的小。

对于序列1,5,8,3,6,7来说,当子序列为1,5,8时,遇到3时,序列已经不能继续变长了。但是,我们可以通过替换,使“整个序列”看上去更小,从而有更大的机会去变长。这样,当替换5-3和替换8-6完成后(此时序列为1,3,6),我们可以在序列末尾添加一个7了。

那为什么复杂度可以是O(NlogN)呢?

关键就在“替换”这一步上,若直接遍历序列替换,每次替换都要O(N)的时间。但是只要我们再次利用LIS的性质——序列是有序的(单调的),就可以用二分查找,在O(logN)的时间内完成一次替换,所以算法的复杂度是O(NlogN)的。

演示:来自https://blog.csdn.net/qq_37555704/article/details/81479623

这是原数组

首先将f[1]更新

然后发现f[1]还是比当前元素大,继续更新 

同上:

这里发现2比1大,于是更新了f[2] 

同上 

这里最后就返回了3 

 实现:

class Solution {
    public int lengthOfLIS(int[] nums) {
        if(nums==null||nums.length==0) return 0;
        int[] dp=new int[nums.length];
		dp[0]=nums[0];
		int j=0;
		for(int i=1;i<nums.length;i++){			
			// 如果小于末尾那么替换掉DP数列中比s[i]大的第一个数。
			if(nums[i]<=dp[j]){
				//去dp数列中寻找比nums[i]大的第一个数,并用nums[i]替换掉该数
				int index=binarySearch(dp,0,j, nums[i]);
				dp[index]=nums[i];
			}else{
				//将每个遍历的数与DP数列的末尾进行比较, 如果大于末尾,
				//则把DP数列长度提1将s[i]放在DP数列的最后,
				dp[++j]=nums[i];	
			}
		}
		//已找到连续递增的子序列,输出序列的长度
		return j+1;
    }
    private int binarySearch(int[] arr, int start,int end,int num) {
		while(start<end){
			int mid=(end-start)/2+start;
			if(arr[mid]<num){
				start=mid+1;
			}else{
				end=mid;
			}
		}
		return end;
	}
}

实现2:九章算法

public class Solution {
    /**
     * @param nums: The integer array
     * @return: The length of LIS (longest increasing subsequence)
     */
    public int longestIncreasingSubsequence(int[] nums) {
        int[] minLast = new int[nums.length + 1];
        minLast[0] = Integer.MIN_VALUE;
        for (int i = 1; i <= nums.length; i++) {
            minLast[i] = Integer.MAX_VALUE;
        }
        
        for (int i = 0; i < nums.length; i++) {
            // find the first number in minLast >= nums[i]
            int index = binarySearch(minLast, nums[i]);
            minLast[index] = nums[i];
        }
        
        for (int i = nums.length; i >= 1; i--) {
            if (minLast[i] != Integer.MAX_VALUE) {
                return i;
            }
        }
        
        return 0;
    }
    
    // find the first number > num
    private int binarySearch(int[] minLast, int num) {
        int start = 0, end = minLast.length - 1;
        while (start + 1 < end) {
            int mid = (end - start) / 2 + start;
            if (minLast[mid] < num) {
                start = mid;
            } else {
                end = mid;
            }
        }
        
        return end;
    }
}


--------------------- 
参考:
原文:https://blog.csdn.net/LxcXingC/article/details/81238008 

http://www.cnblogs.com/grandyang/p/4938187.html

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值