题目

给你一个整数数组 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 。

示例 2:

输入:nums = [0,1,0,3,2,3]
输出:4

示例 3:

输入:nums = [7,7,7,7,7,7,7]
输出:1

提示:

  • 1 <= nums.length <= 2500
  • -104 <= nums[i] <= 104

进阶:

  • 你能将算法的时间复杂度降低到 O(n log(n)) 吗?

解答

  • n2复杂度动态规划

解题思路:

  1. dp[i]:以nums[i]为结尾的最长递增子序列的长度
  2. 递推公式:max(dp[i],dp[j]+1),
  3. dp[i]至少为1(初始化条件)
  4. 遍历顺序:i从小到大 j从大到小,从小到大都可以(0到i之间的每一个元素)
  5. 最终返回dp数组中的最大值

image-20230830164100634

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n = nums.size();
        if (n == 0) return 0;
        vector<int> dp(n, 1); // 初始化dp数组,每个元素默认长度为1
        for (int i = 0; i < n; i++) {
            for (int j = 0; j < i; j++) {
                if (nums[i] > nums[j]) {
                    dp[i] = max(dp[i], dp[j] + 1); // dp数组的更新
                }
            }
        }
        return *max_element(dp.begin(), dp.end());
    }
};
  • n log(n)复杂度贪心

交换状态与状态值

令g[i]表示长度为i+1的IS的末尾元素的最小值

image-20230830172521363

g是严格递增的。

推论1:一次只能更新一个位置,单调递增序列不能有相同元素。

推论2:更新的位置是第一个大于等于nums[i]的数的下标。

算法:在g上用二分查找快速找到第一个大于等于nums[i]的下标j。如果j不存在,那么nums[i]直接加到g末尾;否则修改g[j]为nums[i].

class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        vector<int> tails; // 用于存储递增子序列的末尾元素
        for (int num : nums) {
            // lower_bound 返回一个迭代器,指向容器中第一个大于等于给定值的元素。
            auto it = lower_bound(tails.begin(), tails.end(), num); // 在 tails 中寻找第一个大于等于 num 的元素
            if (it == tails.end()) {
                tails.push_back(num); // 如果找不到,则将 num 添加到末尾
            } else {
                *it = num; // 否则,更新找到的元素,因为它可以作为更长递增子序列的末尾元素
            }
        }
        return tails.size(); // 返回 tails 数组的长度,即为结果
    }
};