LintCode 76: Longest Increasing Subsequence (最经典的LIS题,DP好题)

  1. Longest Increasing Subsequence
    中文English
    Given a sequence of integers, find the longest increasing subsequence (LIS).

You code should return the length of the LIS.

Example
Example 1:
Input: [5,4,1,2,3]
Output: 3

Explanation:
LIS is [1,2,3]

Example 2:
Input: [4,2,4,5,3,7]
Output: 4

Explanation: 
LIS is [2,4,5,7]

Challenge
Time complexity O(n^2) or O(nlogn)

Clarification
What’s the definition of longest increasing subsequence?

The longest increasing subsequence problem is to find a subsequence of a given sequence in which the subsequence’s elements are in sorted order, lowest to highest, and in which the subsequence is as long as possible. This subsequence is not necessarily contiguous, or unique.

https://en.wikipedia.org/wiki/Longest_increasing_subsequence

解法1:DP。时间复杂度O(n^2)。
注意最后的dp[]并不一定是排序的,即dp[n-1]并不一定是答案。
比如说input = [4,2,4,5,3], dp[]=[1,1,2,3,2]。答案是dp[3]=3,并不是dp[4]=2。

class Solution {
public:
    /**
     * @param nums: An integer array
     * @return: The length of LIS (longest increasing subsequence)
     */
    int longestIncreasingSubsequence(vector<int> &nums) {
        int numSize = nums.size();
        if (numSize <= 1) return numSize;
        
        vector<int> DP(numSize, 0);
        int g_maxLen = 0;
        int g_maxId = 0;
    
        DP[0] = 1;
        for (int i = 1; i < numSize; ++i) {
            int maxLen = 0;
            int maxId = 0;
            
            for (int j = 0; j < i; ++j) {
                if (nums[i] > nums[j]) {
                    if (DP[j] > maxLen) {
                        maxLen = DP[j];
                        maxId = j;
                    }
                }
            }
            
            DP[i] = maxLen + 1;
        
            if (DP[i] > g_maxLen) {
                g_maxLen = DP[i];
                g_maxId = i;
            }
        }
        
        return g_maxLen;
    }
};

二刷
dp[i] 表示以nums[i]结尾的LCS的长度。

class Solution {
public:
    /**
     * @param nums: An integer array
     * @return: The length of LIS (longest increasing subsequence)
     */
    int longestIncreasingSubsequence(vector<int> &nums) {
        int n = nums.size();
        if (n == 0) return 0;
        vector<int> dp(n);
        int gMaxLen = 1;
        
        for (int i = 0; i < n; ++i) {
            dp[i] = 1;
            for (int j = 0; j < i; ++j) {
                if (nums[i] > nums[j]) {
                    dp[i] = max(dp[i], dp[j] + 1);
                    gMaxLen = max(gMaxLen, dp[i]);
                }
            }
        }
        
        return gMaxLen;
    }
};

解法2:单调队列+binary search。时间复杂度O(nlogn)。
这个其实跟DP没有关系。
下面这个链接讲的非常好。
https://blog.csdn.net/lw_power/article/details/80758674

代码如下:

// find the first number > num
int binarySearch(vector<int> & minLast, int num) {
    int start = 0, end = minLast.size() - 1;
    while (start + 1 < end) {
        int mid = (end - start) / 2 + start;
        if (minLast[mid] < num) {
            start = mid;
        } else {
            end = mid;
        }
    }

    return end;
}

int longestIncreasingSubsequence(vector<int> & nums) {

    vector<int> minLast(nums.size() + 1, 0);
    minLast[0] = INT_MIN;

    for (int i = 1; i <= nums.size(); i++) {
        minLast[i] = INT_MAX;
    }

    for (int i = 0; i < nums.size(); i++) {
        // find the first number in minLast >= nums[i]
        int index = binarySearch(minLast, nums[i]);
        minLast[index] = nums[i];
    }

    for (int i = nums.size(); i >= 1; i--) {
        if (minLast[i] != INT_MAX) {
            return i;
        }
    }

    return 0;
}

为什么单调队列这个方法可行呢?以上面链接的例子,结合我自己的分析谈一下:
假设input = [3,5,6,2,5,4,5,19,5,6,7,12]。
我们希望每次都能维持一个递增子序列(但不见得是原数组中的递增子序列),并且子序列里面下标为i的元素(为方便期间,下标从1开始算)是原数组中长度为i的递增子序列的最大值中的最小值。设当前子序列为currentSequence。当currentSequence长度为4,假设原数组中有n个长度为4的递增子序列,则将这n个递增子序列末尾元素的最小值设为当前子序列的第4个元素。

  1. input = 3,currentSequence={3}。//刚开始,只有一个长度为1的递增子序列,其最小为3。
  2. input = 5,currentSequence={3, 5}。 //因为新来元素更大,直接加后面。即当前长度为2的递增子序列只有{3,5}这个选择。最大元素的最小值就是5。
  3. input = 6,currentSequence={3,5,6}。//因为新来元素更大,直接加后面。即当前长度为3的递增子序列只有{3,5,6}这个选择,最大元素的最小值就是6。
  4. input = 2,currentSequence={2,5,6}。//因为2比3小,我们希望长度为1的递增子序列的最大元素的最小值应该就变成2了。将2插入到currentSequence中3的位置。注意这里{2,5,6}并不反映原数组中的真实场景。比如说原数组就是 [3,5,6,2]的话,那{2,5,6}肯定不是真实的最长递增子序列,但长度为3是对的。
  5. input = 5,currentSequence={2,5,6}。//因为5已经存在,可以将5插入5的位置或者不变。这样,长度为2的递增子序列的最大元素的最小值还是5。
  6. input = 4,currentSequence={2,4,6}。//因为4比5小,所以将4插入5的位子。这样,长度为2的递增子序列的最大元素的最小值为4。
  7. input = 5, currentSequence={2,4,5}。//因为5比6小,所以将5插入6的位置。这样,长度为3的递增子序列的最大元素的最小值为5。
  8. input = 19, currentSequence={2,4,5,19}。//因为19比5大,直接将19放到后面。这样,长度为4的递增子序列的最大元素的最小值为19。
  9. input = 5, currentSequence={2,4,5,19}。//因为5已经存在,可以将5插入5的位置或者不变。这样,长度为2的递增子序列的最大元素的最小值还是5。
  10. input = 6, currentSequence={2,4,5,6}。//因为6比19小,将6插入到19的位子。这样,长度为4的递增子序列的最大元素的最小值为6。
  11. input = 7, currentSequence={2,4,5,6,7}。//因为7比6大,将7放到最后面。这样,长度为5的递增子序列的最大元素的最小值为7。
  12. input = 12, currentSequence={2,4,5,6,7,12}。//因为12比7大,将12放到最后面。这样,长度为6的递增子序列的最大元素的最小值为12。
    处理结束,返回6。

我们老是维持一个最大元素的最小值就是为了便于把一个更大的数直接添加到后面。

所以单调队列法只能求LIS的长度,并不能求出实际的那个最优递增子序列。
另外,这里的单调队列跟我们常见的那种单调队列/单调栈不一样,那个里面的内容是真的反映了实时的单调队列的内容,而且不能得到最长的单调递增/减序列,因为有些已经被pop掉了。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值