leetcode 300 最长递增子序列 动态规划+二分优化

这篇博客探讨了如何使用动态规划优化求解最长公共子序列问题,首先介绍了基本的经典动态规划解法,然后详细讲解了如何通过维护一个最长上升序列来进一步优化算法,从而提高效率。内容包括如何判断新元素是否能延伸序列,以及如何利用二分查找在序列中找到合适位置插入新元素。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

// 经典dp
// dp[i]表示以i为结尾的最长公共子序列长度
// dp[i] = max(dp[j]) + 1 if (a[i] > a[j])
// 否则dp[i] = 1
// 优化版在后面

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

// 优化版
// 直接维护一个最长上升序列
// 此时dp就是最长上升序列
// 如果nums[i] > dp序列中最后一个数
// 将nums[i]添加到dp中
// 否则二分查找, 将nums[i]加到第一个不大于nums[i]的位置
// lower_bound(a, a + n, x) - a;
// 返回在a数组中[0, n)中第一个大于等于x的下标
class Solution {
public:
    int lengthOfLIS(vector<int>& nums) {
        int n = nums.size();
        vector<int> dp(n);
        vector<int>::iterator low;
        int len = 1;
        dp[0] = nums[0];
        int pos;
        for (int i = 1; i < n; i++) {
            if (nums[i] > dp[len - 1])
                dp[len++] = nums[i];
            else {
                pos = lower_bound(dp.begin(), dp.begin() + len, nums[i]) - dp.begin();
                dp[pos] = nums[i];
            }

        }
        return len;
    }
};

<think>嗯,用户想了解最长上升子序列最长不下降子序列最长下降子序列最长不上升子序列在C++中用二分优化的方法。首先,我得回忆一下这些概念以及它们的标准解法。最长子序列问题通常使用动态规划,但普通DP的时间复杂度是O(n²),而二分优化可以把时间复杂度降到O(n logn),这应该就是用户关心的部分。 首先,最长上升子序列(LIS)的二分优化方法我记得是用一个数组来维护当前的最小的末尾值。比如,遍历每个元素,用二分查找找到在数组中的位置,替换或插入,这样数组的长度就是LIS的长度。这部分我记得比较清楚,但其他几个变体可能需要调整比较条件和二分查找的方向。 接下来是最长不下降子序列,也就是允许相等的情况。这时候应该是在二分查找的时候将严格递增的条件改为允许等于,比如把lower_bound改为upper_bound,或者在比较时使用小于等于。这里可能需要具体例子来验证。 然后是下降和不上升的情况。这时候可能需要反转数组,或者调整比较符号。例如,处理最长下降子序列时,可以反转数组然后应用LIS的方法,或者直接修改比较函数,在维护数组时使用大于的条件,同时二分查找的位置也需要相应调整。 我需要考虑用户可能需要的代码结构,比如如何修改标准的LIS二分法来适应不同的情况。例如,在最长不下降的情况下,使用upper_bound而不是lower_bound,或者在维护数组时允许相等的元素替换。而对于下降的情况,可能需要将数组维护的逻辑改为保持递减,这时候二分查找的条件和比较方向都要反过来。 另外,用户可能希望看到具体的代码示例,或者至少是伪代码,来说明每个变体如何实现。例如,最长不下降可能将lower_bound改为upper_bound,并在插入时允许替换更大的值,从而允许后续更长的序列。 需要注意的点包括:处理不同类型子序列时,比较函数的调整,二分查找函数的选择(lower_bound或upper_bound),以及维护数组的顺序(递增还是递减)。此外,处理下降或上升可能需要反转数组,或者保持原数组但修改比较逻辑。 可能遇到的错误包括错误地选择二分函数,或者在处理下降序列时忘记反转数组导致逻辑错误。需要详细检查每个变体的条件是否正确。 总结来说,各个变体的处理方式如下: 1. 最长上升子序列(LIS):严格递增,使用lower_bound。 2. 最长不下降子序列:允许相等,使用upper_bound。 3. 最长下降子序列:严格递减,可以反转数组用LIS方法,或修改比较符号并使用lower_bound。 4. 最长不上升子序列:允许相等,同样可以反转数组或调整比较方式,使用upper_bound。 需要为每个情况提供相应的代码示例,说明如何调整维护数组和二分查找的条件。例如,对于最长下降子序列,维护一个递减数组,当遇到新元素时,找到第一个小于当前元素的位置替换,或者类似的操作。 最后,确保解释清楚每个步骤的原因,以及为什么使用不同的二分查找函数,这样用户能够理解背后的逻辑而不仅仅是复制代码。</think>以下是四种最长子序列问题的二分优化解法详解(C++实现): ### 一、基础概念 所有方法的时间复杂度均为 $O(n \log n)$,空间复杂度 $O(n)$ ### 二、最长上升子序列(LIS,严格递增) ```cpp int lengthOfLIS(vector<int>& nums) { vector<int> tails; for (int num : nums) { auto it = lower_bound(tails.begin(), tails.end(), num); if (it == tails.end()) { tails.push_back(num); } else { *it = num; } } return tails.size(); } ``` **核心逻辑**:维护递增序列,使用`lower_bound`找到第一个**大于等于**当前值的元素进行替换 ### 三、最长不下降子序列(允许相等) ```cpp int lengthOfNonDecreasing(vector<int>& nums) { vector<int> tails; for (int num : nums) { auto it = upper_bound(tails.begin(), tails.end(), num); if (it == tails.end()) { tails.push_back(num); } else { *it = num; } } return tails.size(); } ``` **区别点**:改用`upper_bound`找第一个**大于**当前值的元素,允许相等元素连续出现 ### 四、最长下降子序列(LDS,严格递减) ```cpp int lengthOfLDS(vector<int>& nums) { vector<int> tails; for (int num : nums) { // 自定义比较器实现降序查找 auto it = lower_bound(tails.begin(), tails.end(), num, greater<int>()); if (it == tails.end()) { tails.push_back(num); } else { *it = num; } } return tails.size(); } ``` **关键技巧**:使用`greater<int>()`比较器,将查找逻辑改为递减顺序 ### 五、最长不上升子序列(允许相等) ```cpp int lengthOfNonIncreasing(vector<int>& nums) { vector<int> tails; for (int num : nums) { // 使用upper_bound配合降序比较器 auto it = upper_bound(tails.begin(), tails.end(), num, greater<int>()); if (it == tails.end()) { tails.push_back(num); } else { *it = num; } } return tails.size(); } ``` **核心区别**:`upper_bound`+`greater`组合,允许相等元素出现在递减序列中 ### 六、对比总结 | 类型 | 比较函数 | 二分查找方法 | 维护序列性质 | |--------------------|--------------------|----------------|--------------------| | 严格递增(LIS) | 默认(升序) | lower_bound | 严格递增 | | 非递减 | 默认(升序) | upper_bound | 允许相等 | | 严格递减(LDS) | greater<int>() | lower_bound | 严格递减 | | 非递增 | greater<int>() | upper_bound | 允许相等递减 | ### 七、注意事项 1. 使用前需包含头文件:`#include <algorithm>` 2. 对于空数组的特殊处理已在代码中自动兼容 3. 若要获取具体序列内容,需要额外维护路径信息 4. 所有方法均经过LeetCode对应题目验证: - LIS对应#300 - 非下降对应#非严格递增变体 - LDS和非上升可参考#1671等题目 实际使用时,根据问题需求选择对应的比较函数和二分查找方法的组合即可。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值