摆动序列(力扣376)

本文详细解析了LeetCode中的摆动序列问题,通过动态规划和贪心策略找到给定整数数组中摆动序列的最长子序列长度,涉及单调坡处理和特殊情况分析。

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



在这里插入图片描述

题目

Problem: 376. 摆动序列

如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 摆动序列 。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。

  • 例如,[1, 7, 4, 9, 2, 5] 是一个 摆动序列 ,因为差值(6, -3, 5, -7, 3) 是正负交替出现的。
  • 相反,[1, 4, 7, 2, 5][1, 7, 4, 5, 5]
    不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。

子序列 可以通过从原始序列中删除一些(也可以不删除)元素来获得,剩下的元素保持其原始顺序。

给你一个整数数组 nums ,返回 nums 中作为 摆动序列最长子序列的长度

示例 1:

输入:nums = [1,7,4,9,2,5]
输出:6
解释:整个序列均为摆动序列,各元素之间的差值为 (6, -3, 5, -7, 3) 。

示例 2:

输入:nums = [1,17,5,10,13,15,10,5,16,8]
输出:7
解释:这个序列包含几个长度为 7 摆动序列。
其中一个是 [1, 17, 10, 13, 10, 16, 8] ,各元素之间的差值为 (16, -7, 3, -3, 6, -8) 。

示例 3:

输入:nums = [1,2,3,4,5,6,7,8,9]
输出:2

前知

力扣(LeetCode)第376道题目是“摆动序列”(Wiggle Subsequence),属于动态规划和贪心算法的类型。这道题的要求是找到给定整数序列中最长的摆动子序列的长度。摆动序列是指相邻元素之间的差异在正负之间交替变化。


题解

一、思路

要想得到最大摆动序列的话,只要将单调坡上的结点删除,保留两端的结点,这个坡度就有两个峰值,是局部最优,由局部最优推出全局最优,由于题目只要求返回最大摆动序列的长度,用result变量统计,不用删除结点,用后一个值减去前一个值,得到前prediffcurdiff的值大小,如果prediff < 0 && curdiff > 0 或者 prediff > 0 && curdiff < 0 则可以确定存在峰值需要result++
,还有三种特殊情况在下面的解题方法还需要考虑一下

在这里插入图片描述

二、解题方法

本题大概代码是下面这样的:

//只有一个就返回摆动序列长度为1
if (nums.length <= 1) {
    return nums.length;
}
//计数
int result = 1;

for (int i = 1; i < nums.length; i++) {
     //得到当前差值
     int curDiff = nums[i] - nums[i - 1];
     //如果当前差值和上一个差值为一正一负,出现波动就统计
     if ((prediff < 0 && curdiff > 0) || (prediff > 0 && curdiff < 0)) {
          result++;
     }
     preDiff = curDiff;
}
return count;

三种特殊情况:

  1. 上下坡中有平坡:统一删除靠近左边平坡的点,到最后一个结点时的情况是prediff = 0 && curdiff < 0也要进行记录,所以左边的prediff都要加上与0判断相等的情况
if ((curDiff > 0 && preDiff <= 0) || (curDiff < 0 && preDiff >= 0))

image.png

  1. 数组首尾两端:当数组只有两个结点时,无法计算出prediffcurdiff,让左边prediff初始为0,这样三个点会根据情况1自动删除左面的一个结点,统计result加1为2,result初始为1,因为nums数组至少都有一个元素,也就是摆动序列长度为1
//当前差值
int curDiff = 0;
//上一个差值
int preDiff = 0;

image.png

  1. 单调坡中有平坡:prediff不能每走一步都进行更新,要等出现波动时,再让prediff等于curdiff。
    在下图中实际上只有数组首尾的1,4是摆动序列里的,result应该为2,但是在平坡的最后一个2的地方因为每走一步prediff都更新下一步的,所以在2的地方也计算了一个波动,只要把prediff更新操作放到if判断里面即可
if ((curDiff > 0 && preDiff <= 0) || (curDiff < 0 && preDiff >= 0)) {
     count++;
     preDiff = curDiff;  // 注意这里,只在摆动变化的时候更新prediff
}

image.png

三、Code

class Solution {
    public int wiggleMaxLength(int[] nums) {
        if (nums.length <= 1) {
            return nums.length;
        }
        //当前差值
        int curDiff = 0;
        //上一个差值
        int preDiff = 0;
        int count = 1;
        for (int i = 1; i < nums.length; i++) {
            //得到当前差值
            curDiff = nums[i] - nums[i - 1];
            //如果当前差值和上一个差值为一正一负
            //等于0的情况表示初始时的preDiff
            if ((curDiff > 0 && preDiff <= 0) || (curDiff < 0 && preDiff >= 0)) {
                count++;
                preDiff = curDiff;
            }
        }
        return count;
    }
}

总结

以上就是针对这道题的刷题笔记,用到了贪心算法从单调坡度上删除中间的结点保留两端的局部最优解推导出整个序列具有最多的局部峰值,达到最长摆动序列,希望这篇题解能够帮助到你解决这个问题。如果有任何疑问或者建议,欢迎留言讨论🌹

<think>好的,用户现在想解决LeetCode上关于寻找长度为k且和最大的子序列的问题。首先,我需要回忆相关的题目和解法。根据用户提供的引用,特别是引用[2]和[5],LeetCode 2099题就是这个问题。用户提到需要算法或解题思路,所以我要先整理已有的信息。 首先,子序列和子数组不同,子序列不要求连续,但顺序必须保持。题目要求长度为k,且和最大。常见的思路可能是先排序然后取最大的k个数,但这样会破坏顺序,导致得到的子序列顺序与原数组不符。比如,如果数组是[3,1,4,2],k=2,排序后最大的两个数是4和3,但原数组中4在3之后,所以正确的子序列应该是[3,4],但直接排序选最大的两个可能得到[4,3],顺序不对。因此,直接排序不行,需要考虑其他方法。 引用[5]中提到了选择排序的方法,每次选出剩余元素中的最大值,并记录其索引,然后将该位置置为None,避免重复选择。最后根据记录的索引排序,得到正确顺序的子序列。这种方法的时间复杂度是O(nk),当k较小时比较高效。但若k接近n,则可能达到O(),不过题目可能数据规模不大,可以接受。 另一种方法是使用堆(优先队列),如引用[2]提到的堆解法。具体来说,可以维护一个大小为k的最大堆,或者使用堆来快速找到最大的k个元素及其索引。不过需要注意保持元素的顺序。可能的步骤是:遍历数组,将元素和索引存入堆中,按值从大到小排序。取出前k个元素后,根据索引排序,得到正确顺序的子序列。这种方法的时间复杂度是O(n log n),因为堆的插入操作是O(log n),而最后对k个元素排序是O(k log k)。对于较大的n,这种方法可能更优。 然后,我需要考虑动态规划是否有适用的可能,但根据问题特点,子序列需要保持顺序,并且长度固定为k,和最大。动态规划的状态可能设计为dp[i][j],表示前i个元素中长度为j的子序列的最大和。状态转移方程需要考虑是否选择当前元素。但这样的动态规划时间复杂度是O(nk),可能和选择排序方法相当,但实现起来稍微复杂。不过引用中没有提到动态规划解法,可能更推荐堆或选择方法。 现在需要比较这两种方法的优劣。堆方法的时间复杂度更低,尤其是当k较大时。而选择排序方法在k较小时更优,实现简单。根据题目给出的示例代码,引用[5]中的解法就是选择排序的思路,每次选最大的元素,记录索引,最后按索引排序输出。这可能更适合快速实现,尤其是在编程比赛中。 另外,还需要注意重复元素的情况。例如,如果有多个相同的最大值,需要正确选择它们的索引,以保持子序列的顺序。例如数组[5,3,5,2],k=2,应选择第一个5和第二个5,子序列是[5,5],但它们的索引分别是0和2,所以最终输出应为[5,5]。 总结可能的解题步骤: 1. 遍历数组,找到最大的k个元素,并记录它们的值和原始索引。 2. 根据记录的索引进行排序,确保子序列的顺序与原数组一致。 3. 提取对应的值形成结果子序列。 在实现时,需要注意如何处理元素的选择,确保每次选的是当前剩余元素中的最大值,同时记录索引。引用[5]中的代码通过将已选元素置为None来避免重复选择,这可能有效,但修改原数组是否允许需要考虑题目要求。如果题目不允许修改原数组,可能需要复制数组或使用其他方式标记已选元素。 另外,堆方法如何实现?例如,可以将所有元素和索引一起存入堆中,按值从大到小排序。取出前k个元素后,根据索引排序,然后输出对应的值。这样可以避免修改原数组,并且时间复杂度为O(n + k log k),因为堆化需要O(n)时间(如果使用heapq.nlargest),然后对k个元素排序需要O(k log k)。这可能更高效,尤其是当n较大时。 因此,用户的问题需要给出这两种可能的解法,并比较它们的优缺点。同时,引用中的示例代码使用的是选择排序的方法,而引用[2]提到了堆解法,所以需要详细说明这两种方法,并给出代码示例。 另外,需要注意子序列和子数组的区别,确保解答中明确说明这一点。例如,子序列不需要连续,但顺序必须保持,而子数组是连续的。 最后,根据用户提供的引用,特别是引用[2]和[5],需要将这两种方法整合到回答中,并正确引用对应的参考资料。</think>### LeetCode 2099. 找到和最大的长度为 K 的子序列 #### 问题分析 题目要求从整数数组 $nums$ 中选出长度为 $k$ 的子序列,使得其和为最大,且保持元素的原始顺序[^2]。由于子序列不要求连续,但必须保持顺序,直接排序会破坏索引关系,因此需要结合数值大小和索引顺序进行选择。 --- ### 核心解法 #### 1. 选择排序法(贪心思想) **思路**:每次选择当前剩余元素中的最大值,记录其索引,并将该位置标记为已选(例如置为 `None`),重复此过程直到选满 $k$ 个元素。最后根据索引顺序输出结果。 - **时间复杂度**:$O(nk)$,适用于 $k$ 较小的情况。 - **空间复杂度**:$O(1)$(原地修改数组)或 $O(n)$(使用辅助数组)。 ```python def maxSubsequence(nums, k): ans = [] for _ in range(k): max_val, max_idx = -float('inf'), -1 for idx, val in enumerate(nums): if val is not None and val > max_val: max_val, max_idx = val, idx ans.append((max_idx, max_val)) nums[max_idx] = None # 标记已选元素 # 按索引排序后提取值 return [val for idx, val in sorted(ans)] ``` --- #### 2. 堆(优先队列)优化法 **思路**:利用最大堆快速获取前 $k$ 个最大元素及其索引,再按索引排序恢复原始顺序。 - **时间复杂度**:$O(n \log n + k \log k)$,适合 $k$ 较大的情况。 - **空间复杂度**:$O(n)$。 ```python import heapq def maxSubsequence(nums, k): # 将元素和索引存入堆,按值降序排列 heap = [(-val, idx) for idx, val in enumerate(nums)] heapq.heapify(heap) # 取前k个元素(值最大的k个) selected = [] for _ in range(k): val, idx = heapq.heappop(heap) selected.append((idx, -val)) # 按索引排序后提取值 selected.sort() return [val for idx, val in selected] ``` --- ### 关键点对比 | 方法 | 适用场景 | 时间复杂度 | 空间复杂度 | |--------------|------------------|------------------|------------| | 选择排序法 | $k$ 较小 | $O(nk)$ | $O(1)$ | | 堆优化法 | $k$ 较大或通用 | $O(n \log n)$ | $O(n)$ | --- ### 典型用例 1. **输入**:$nums = [3,1,4,2], k = 2$ **输出**:$[3,4]$(子序列索引为0和2,和为7) 2. **输入**:$nums = [5,3,5,2], k = 2$ **输出**:$[5,5]$(索引0和2,和为10) ---
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

花花花1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值