LeetCode 用动态规划解决最长上升子序列(LIS)问题(C++)

使用动态规划算法解决最长上升子序列问题,详细解释思路并给出C++实现,执行结果显示耗时和内存使用情况。

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

问题描述

给定一个无序的整数数组,找到其中最长上升子序列的长度,序列可以不连续。

示例1:
输入: [10,9,2,5,3,7,101,18]
输出: 4
解释: 最长的上升子序列是 [2,3,7,101],它的长度是 4。

示例2:
输入: [10,9,2,7,3,6,5,8,12,9,11]
输出: 4
解释: 最长的上升子序列是 [2,3,5,8,9,11],它的长度是 6。

算法思想

利用动态规划的思想,将原始问题分解为小问题来求解。
1.定义一个动态数组名为:maxlenmaxlenmaxlen用来存储每个元素的LIS的值,即maxlen[0]maxlen[0]maxlen[0]~ maxlen[i]maxlen[i]maxlen[i]存储着nums[0]nums[0]nums[0]~nums[i]nums[i]nums[i]的LIS值。
2.当第i+1i + 1i+1项时,maxlen[i+1]maxlen[i + 1]maxlen[i+1]的初始化为1,毋庸置疑。从numsnums

<think>嗯,用户想了解最长上升子序列、最长不下降子序列、最长下降子序列和最长不上升子序列在C++中用二分法优化的方法。首先,我得回忆一下这些概念以及它们的标准解法。最长子序列问题通常使用动态规划,但普通DP的时间复杂度是O(),而二分优化可以把时间复杂度降到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、付费专栏及课程。

余额充值