题目:最长递增子序列

解法一:动态规划

状态定义:dp[ i ]指的是“以第 i 个元素结尾的子序列的最大长度”

状态转移:dp[ i ] = max( dp[ i ], dp[ j ] + 1 ),其中 j 是不大于 i 的正整数;

方程的意义是——假设正在处理 nums[i],我们想把它接在一个现有的子序列后面。

为了保证“严格递增”,必须满足两个条件:

1. **位置在前后**:那个数必须在 i 之前(即 j < i)。
2. **数值从小到大**:那个数必须比 `nums[i]` 小(即 nums[j] < nums[i])。
 1int lengthOfLIS(vector<int>& nums)
 2{
 3    if(nums.empty()) return 0;
 4    std::vector<int> dp(nums.size(), 1);
 5    int res = 1;
 6    for(int i = 0; i < nums.size(); i++)
 7    {
 8        for(int j = 0; j < i; j++)
 9        {
10            if(nums[j] < nums[i])
11            {
12                dp[i] = std::max(dp[i], dp[j] + 1);
13            }
14        }
15        res = std::max(res, dp[i]);
16    }
17    return res;
18}

补充解法:二分查找维护最小末尾

维护一个数组 dp,dp【i】的含义是:长度为 i + 1 的子数组的末尾元素的最小值

然后遍历数组 nums,在 dp 中通过二分查找,看看 num 的位置在哪里

  • 如果不存在任何一个数大于等于 num,也就是 it = num.end(),就将 num 接入 dp 的末尾,意味着子序列的长度可以加一
  • 如果当前数组中已经有一个数大于等于 num 了,就将这个数替换为 num(降低末尾元素的值,确保后续可以有更多元素加入)
  • 时间复杂度:O(n logn),其中 n 为 nums 的长度
  • 空间复杂度:O(n)

 1int lengthOfLIS(vector<int>& nums)
 2{
 3    std::vector<int> dp;
 4
 5    for(int num : nums)
 6    {
 7        auto it = std::lower_bound(dp.begin(), dp.end(), num);
 8        if(it == dp.end())
 9        {
10            dp.push_back(num);
11        }
12        else
13        {
14            *it = num;
15        }
16    }
17    return dp.size();
18}

来源:语雀原笔记

补充解法:原地修改

直接对传入的 nums 数组进行修改,避免再创建一个新数组

lengthOfLIS()

 1int lengthOfLIS(vector<int>& nums)
 2{
 3    // 从第一个元素开始
 4    auto end = nums.begin();
 5
 6    for(int num : nums)
 7    {
 8        // 在区间内找到第一个大于等于 num 的位置
 9        auto it = lower_bound(nums.begin(), end, num);
10
11        // 将原来 g[j] 的值(一个较大的数)替换成更小的 num
12        // 为什么直接认为 num 一定小于之前的末尾元素?
13        /* 因为 lower_bound(first, last, x) 返回的是第一个满足 元素 >= x 的位置。
14        因此,当 it != end 时,*it >= x 恒成立。
15        于是执行 *it = x 后:
16        如果 *it > x,新值变小(这正是我们想要的,因为要降低该长度的最小末尾)。
17        如果 *it == x,新值不变(没有影响)。
18        没有可能出现 *it < x 的情况,所以不存在“当前元素大于原末尾”的问题*/
19        *it = num;
20
21        if(it == end)
22        {
23            // 此时将长度加一
24            end++;
25        }
26    }
27    return end - nums.begin();
28}

来源:语雀原笔记