给你一个整数数组 nums ,找到其中最长严格递增子序列的长度。

子序列是由数组派生而来的序列,删除(或不删除)数组中的元素而不改变其余元素的顺序。例如,[3,6,2,7] 是数组 [0,3,1,6,2,2,7] 的子序列。

示例 1:

输入:nums = [10,9,2,5,3,7,101,18]
输出:4
解释:最长递增子序列是 [2,3,7,101],因此长度为 4 。

算法1(动态规划)

时间复杂度:\(O(N^2)\)
分析题目不难发现寻找最长递增子序列的过程存在着重叠子问题,即例如在寻找12345这个序列的最长递增子序列时,也会先求出1234的最长递增子序列。同时该问题也符合最优子结构,即子问题之间相互独立,不存在制约关系。所以我们可以考虑使用动态规划的求解方法。
定义dp数组:dp[i]表示以nums[i]结尾的最长递增子序列的长度。
根据上述定义,我们可以推出base case为dp[i]的初始值为1,即一个以nums[i]结尾序列的最长递增子序列至少包含nums[i]自己。
计算以nums[i]结尾的最长递增子序列时,将nums[i]nums[0]~nums[i-1]依次比较,若大于则最长子序列长度在原序列的情况下加一。

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        vector<int> dp(nums.size(), 1);
        for (int i = 0; i < nums.size(); i ++ ) {
            for (int j = 0; j < i; j ++ ) {
                if (nums[i] > nums[j]) {
                    dp[i] = max(dp[i], dp[j] + 1);
                }
            }
        }

        int res = 0;
        for (int i = 0; i < dp.size(); i ++ ) {
            res = max(res, dp[i]);
        }
        return res;
    }
};

如何确定动态规划的状态转移关系?
1、明确dp数组的定义
2、假设已知dp[0...i-1],求出dp[i]的结果。完成了这一步,整个问题也就基本解决了。

算法2(二分查找)

时间复杂度:\(O(N\log N)\)

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int piles = 0; //纸牌堆数
        vector<int> top(nums.size()); //记录每堆牌顶的值

        for (int i = 0; i < nums.size(); i ++ ) {
            int poker = nums[i]; //当前要处理的纸牌
            
            //二分查找--将区间划分为 [l,mid],[mid+1, r]
            //由于遇到多个可选择堆的时候要放到最左边的堆上(这样可以保证牌堆顶的牌有序)
            int left = 0, right = piles;
            while (left < right) {
                int mid = left + right >> 1;
                if (top[mid] >= poker) right = mid;
                else left = mid + 1;
            }

            //如果没有找到合适位置 则新建一个牌堆
            if (left == piles) piles ++;
            top[left] = nums[i];
        }
        return piles;
    }
};