算法系列——Longest Increasing Subsequence

来源:互联网 发布:iphone字体软件 编辑:程序博客网 时间:2024/06/07 21:34

题目描述

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

For example,
Given [10, 9, 2, 5, 3, 7, 101, 18],
The longest increasing subsequence is [2, 3, 7, 101], therefore the length is 4. Note that 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?

解题思路

动态规划法

我们用一维的数组表示状态,即dp[i]表示以第i个数结尾的最长上升子序列的最长长度,那么很容易推出状态转移方程dp[i] = max(dp[j]+1),其中1<=j

排序法+二分查找

复杂度

时间 O(NlogN) 空间 O(N)

思路
在1,3,5,2,8,4,6这个例子中,当到6时,我们一共可以有四种
(1)不同长度
(2)且保证该升序序列在同长度升序序列中末尾最小
的升序序列

11,21,3,41,3,5,6

这些序列都是未来有可能成为最长序列的候选人。这样,每来一个新的数,我们便按照以下规则更新这些序列

  1. 如果nums[i]比所有序列的末尾都大,或等于最大末尾,说明有一个新的不同长度序列产生,我们把最长的序列复制一个,并加上这个nums[i]。
  2. 如果nums[i]比所有序列的末尾都小,说明长度为1的序列可以更新了,更新为这个更小的末尾。
  3. 如果在中间,则更新那个末尾数字刚刚大于等于自己的那个序列,说明那个长度的序列可以更新了。

比如说,如果再来一个0,那就是第一种情况,更新序列为

01,21,3,31,3,5,6

比如这时,如果再来一个9,那就是第二种情况,更新序列为

    1    1,2    1,3,4    1,3,5,6    1,3,5,6,9

如果再来一个3,那就是第三种情况,更新序列为

11,21,3,31,3,5,6

前两种都很好处理,O(1)就能解决,主要是第三种情况,实际上我们观察直到6之前这四个不同长度的升序序列,他们末尾是递增的,所以可以用二分搜索来找到适合的更新位置。

注意

二分搜索时如果在tails数组中,找到我们要插入的数,也直接返回那个结尾的下标,虽然这时候更新这个结尾没有意义,但少了些判断简化了逻辑。

程序实现

动态规划

class Solution {    public int lengthOfLIS(int[] nums) {        if(nums==null||nums.length==0)            return 0;        int[] dp=new int[nums.length];        dp[0]=1;        int max=1;        for(int i=1;i<nums.length;i++){             // 找到dp[0]到dp[i-1]中最大的升序序列长度且nums[j]<nums[i]            for(int j=0;j<i;j++){                if(nums[j]<nums[i])                    dp[i]=Math.max(dp[i],dp[j]);            }             // 加1就是该位置能构成的最长升序序列长度            dp[i]+=1;            // 更新全局长度            max=Math.max(max,dp[i]);        }        return max;    }}

排序+二分查找

class Solution {    public int lengthOfLIS(int[] nums) {       if(nums.length == 0){            return 0;        }        // len表示当前最长的升序序列长度(为了方便操作tails我们减1)        int len = 0;        // tails[i]表示长度为i的升序序列其末尾的数字        int[] tails = new int[nums.length];        tails[0] = nums[0];        // 根据三种情况更新不同升序序列的集合        for(int i = 1; i < nums.length; i++){            if(nums[i] < tails[0]){                tails[0] = nums[i];            } else if (nums[i] > tails[len]){                tails[++len] = nums[i];            } else {            // 如果在中间,则二分搜索                tails[binarySearch(tails, 0, len, nums[i])] = nums[i];            }        }        return len + 1;    }    private int binarySearch(int[] tails, int min, int max, int target){        while(min <= max){            int mid = min + (max - min) / 2;            if(tails[mid] == target){                return mid;            }            if(tails[mid] < target){                min = mid + 1;            }            if(tails[mid] > target){                max = mid - 1;            }        }        return min;    }}
原创粉丝点击