[LeetCode]Longest Increasing Subsequence

题目链接:https://leetcode.com/problems/longest-increasing-subsequence/

题目内容:

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(n^2) complexity.

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

题目解法

这是一道典型的动态规划问题,通常有两种解法,一种自然的思想时间复杂度为O(n^2),而另一种巧妙地思路可以利用二分查找把时间复杂度降低到O(nlogn)。下面分别介绍这两种做法。

首先我们约定nums为输入容器,下标从0开始。

  • 解法一

    dp[i]表示以nums[i]结尾的最长递增子列(LIS)的长度。由于最短的LIS就是自己本身,因此我们先初始化dp[.]=1。接着,从前到后遍历整个nums容器,按照下面的规则更新dp。

    • dp[i] = max{dp[j]+1|0<=j<i,nums[i] > nums[j]}

    • 通俗来说,也就是第i个位置为结尾的LIS长度,取决于前面0~i-1位置的LIS长度,如果前面的LIS的最后一个元素比当前位置的元素小(nums[i] > nums[j]),则可以把当前的结尾加入到前面的LIS尾部,构成一个比原来长度+1的LIS。因为我们是从前到后来更新dp,因此在更新dp[i]时已经得到了所有的dp[j],j<i,我们只需要从中选取最大的。在遍历结束后,也就得到了以每个数为结尾的LIS,只要把dp排序,取出最大的元素即为整个序列的LIS。

    代码如下:

    class Solution {
    public:
    int lengthOfLIS(vector<int>& nums) {
        int cnt = nums.size();
        int *dp = (int*)malloc(cnt*sizeof(int));
        for(int i = 0; i < cnt; i++) dp[i] = 1;
        for(int i = 1; i < cnt; i++){
            for(int j = 0; j < i; j++){
                if(nums[j] < nums[i] && dp[i] < dp[j] + 1){
                    dp[i] = dp[j] + 1;
                }
            }
        }
        sort(dp,dp+cnt);
        return dp[cnt - 1];
    }
    };
  • 解法二

    d[len]表示长度为len的最长子列的最小末尾元素,这里之所以说是最小,是因为可能出现多个解,而显然其中结尾最小的最具有变得更长的潜力,因此我们应该选取结尾最小的作为最优解。当我们遍历nums[i]中的每个元素时,都能在d[len]中找到合适的位置插入它,规则为:

    • nums[i]d[len]还大,说明可以插入到当前LIS的尾部,也就是可以找到一个更长的LIS,这时候我们更新d[++len]=nums[i],这样不仅更新了LIS的尾部,也更新了长度。

    • nums[i]不比d[len]大时,并不能改变LIS的长度,但是可以对LIS进行优化,比如更新一个长度比len小的LIS的尾部元素为更小的值,虽然一次这样的更新可能无法带来len的增加,但是可以减小d[len],例如序列10,9,2,5,3,7,我们开始会得到d[1]=10,接着9的到来会更新d[1]=92的到来会更新d[1]=2,只有这样,当5到来时,才能得到5>d[1],从而得到d[2]=5

    按照上面的描述,我们发现算法的关键不再是对len的计算,而是元素插入位置的计算,插入元素的时间复杂度即为找到LIS的时间复杂度,因此我们使用二分查找来插入,由于我们所谓的插入其实是一种覆盖,因此应该找到下界来防止d[len]被错误地覆盖,使用STL的lower_bound即可。

    代码如下:

    class Solution {
    public:
    int lengthOfLIS(vector<int>& nums) {
        int cnt = nums.size();
        if(cnt == 0) return 0;
        int *d = (int*)malloc((cnt+1)*sizeof(int));
        int len = 1;
        d[len] = nums[0];
        for(int i = 1; i < cnt; i++){
            if(nums[i]>d[len]){
                d[++len]=nums[i];
            }else{
                int pos = lower_bound(d,d+len,nums[i]) - d;
                d[pos] = nums[i];
            }
        }
        return len;
    }
    };

你可能感兴趣的:(LeetCode,C++,dp,动态规划,LIS)