题目描述

✅ 376. 摆动序列

image-20260928235341799

image-20260928235341800

题意分析

从数组中按原顺序选择若干元素,使选出元素之间的相邻差值严格正负交替,返回最长长度。子序列可以跳过元素,不要求连续;第一个非零差值可以为正,也可以为负。

相等元素产生的零差值不能算一次摆动。单个元素本身是长度为 $1$ 的摆动序列,所以题目给定的非空数组至少有一个元素可选。

解法:峰谷贪心

核心思路

[!blue]

摆动只关心上升、下降是否交替,而不关心差值的绝对大小。把连续同方向的变化看成一段,在一段内继续向同方向移动,不能直接为摆动序列增加一个元素,否则相邻两次差值会同号。

但同方向移动可以让当前端点更有利:

  • 正在上升时,用后面更高的点替换末尾的高点。它仍高于前一个低点,而且原来能接在旧高点后面的更低值,也一定低于新高点,因此不会失去后续下降的机会。
  • 正在下降时,用后面更低的点替换末尾的低点。它仍低于前一个高点,并让后续上升至少同样容易。

所以可以把每段同方向变化压缩到最有利的端点,只在方向反转时增加长度,这样会保留交替的峰和谷。去掉零差值后,若共有 r 段连续同号的方向,贪心便能选出 r + 1 个元素。

这个长度也达到上限:子序列中任意一次上升或下降,在它跨过的原数组区间内,都至少需要一个同号的相邻差值。这些区间包含的相邻差值按顺序互不重叠,而相邻两次摆动方向相反,不可能由原数组中的同一个方向段提供。因此最多形成 r 次摆动,即 r + 1 个元素。

代码只求长度,不需要真的保存或替换这些端点。扫描到相邻差值 diff = nums[i] - nums[i - 1] 时,用 prevDiff 保存此前接受的非零方向,只使用它的正负号:

  • diff > 0 且 prevDiff <= 0,开始了上升方向,答案加一。
  • diff < 0 且 prevDiff >= 0,开始了下降方向,答案加一。
  • 差值同号时只是隐含替换当前端点;差值为 $0$ 时是平台,两者都不增加答案,也不改变已记录的方向。

初始 answer = 1、prevDiff = 0。零表示还没有方向,因此第一次遇到非零差值时也会增加答案,选出第二个元素。若数组只有一个元素或所有元素相等,就一直保留答案 $1$。平台不能把方向清零,否则平台之后同方向的延伸会被误当成一次新的摆动。

解题步骤

  1. 初始化 answer = 1、prevDiff = 0。
  2. 从第二个元素开始,计算它与前一个元素的差值 diff。
  3. 若 diff 非零,且与 prevDiff 反号或 prevDiff 仍为零,令答案加一,并将 prevDiff 更新为 diff。
  4. 同方向和零差值直接略过;遍历结束后返回 answer。

代码实现

class Solution {
    public int wiggleMaxLength(int[] nums) {
        int answer = 1;
        int prevDiff = 0;

        for (int i = 1; i < nums.length; i++) {
            int diff = nums[i] - nums[i - 1];

            // 仅接受非零方向切换,平台不清除此前方向
            if ((diff > 0 && prevDiff <= 0) || (diff < 0 && prevDiff >= 0)) {
                answer++;
                prevDiff = diff;
            }
        }

        return answer;
    }
}
func wiggleMaxLength(nums []int) int {
    answer, prevDiff := 1, 0
    for i := 1; i < len(nums); i++ {
        diff := nums[i] - nums[i-1]
        // 仅接受非零方向切换,平台不清除此前方向
        if (diff > 0 && prevDiff <= 0) || (diff < 0 && prevDiff >= 0) {
            answer++
            prevDiff = diff
        }
    }
    return answer
}

复杂度分析

  • 时间复杂度:$O(n)$,只扫描一次。
  • 空间复杂度:$O(1)$,只保存方向和长度。

关键点总结

[!green]

  • 上升段保留更高端点,下降段保留更低端点,不会减少后续反向选择的机会。
  • 长度等于非零方向段数加一;只需要记录方向,无需构造实际子序列。
  • 零差值既不计数,也不改变此前方向;prevDiff = 0 只负责接纳最初的非零差值。

易错点总结

[!yellow]

  • 直接统计不相等的相邻对,会把连续上升算成多次摆动。
  • 遇到相等值将方向清零,会把同方向的平台两侧重复计数。

相似题目

题目 难度 关联与区别
300. 最长递增子序列 中等 原题要求一直递增,本题要求相邻差值符号交替,可压缩为上升/下降状态。
978. 最长湍流子数组 中等 原题要求连续区间,本题可跳过不利元素形成子序列,处理相等值和转折的方式不同。
转载与许可
作者
链接 https://hgnulb.github.io/blog/2025/79699960
许可 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处!