LeetCode 376.摆动序列:贪心策略下的极值点筛选

一、问题定义与核心特征

1.1 摆动序列的定义

摆动序列是指一个序列中,相邻元素的差值呈现正负交替的特征。即:

  • 若后一个差值为正,则前一个差值必为负;
  • 若后一个差值为负,则前一个差值必为正;
  • 差值为0的情况不参与交替(视为平坡,可忽略)。

例如:

  • 序列 [1,7,4,9,2,5] 是摆动序列,差值为 6,-3,5,-7,3(正负交替);
  • 序列 [1,2,3,4] 不是摆动序列,差值为 1,1,1(全为正,无交替)。

1.2 问题目标

给定一个整数数组 nums,返回其最长摆动子序列的长度
注:子序列可通过删除部分元素(或不删)得到,不要求连续,但元素顺序需与原数组一致。

二、解题思路:贪心算法与极值点筛选

2.1 核心观察

摆动序列的本质是连续的极值点序列。例如:

  • 上升后下降(峰)、下降后上升(谷)的点,都是摆动序列的关键节点;
  • 平坡(差值为0)不会改变摆动方向,可直接跳过。

因此,最长摆动序列的长度 = 数组中有效极值点的数量 + 1(初始元素计为1)。

2.2 贪心策略

通过记录前一个差值当前差值,判断是否出现方向交替,若交替则计数加1,同时更新前一个差值。具体规则:

  1. 若当前差值与前一个差值异号(乘积 < 0),说明出现摆动,计数加1;
  2. 若前一个差值为0(初始状态或平坡后),当前差值非0,说明首次出现摆动方向,计数加1;
  3. 差值为0时,不改变计数和前一个差值(跳过平坡)。

三、代码逐行解析

3.1 边界条件处理

if (nums.length <= 1) {
    return nums.length;
}
  • 若数组长度为0或1,本身就是最长摆动序列(无需处理),直接返回长度。

3.2 核心变量定义

int curDiff = 0;  // 当前相邻元素的差值(nums[i] - nums[i-1])
int preDiff = 0;  // 前一个有效差值(用于判断交替)
int cnt = 1;      // 摆动序列长度(初始值为1,至少包含第一个元素)

3.3 遍历与判断逻辑

for (int i = 1; i < nums.length; i++) {
    curDiff = nums[i] - nums[i-1];  // 计算当前差值
    
    if (curDiff == 0) {
        continue;  // 平坡,跳过(不影响摆动方向)
    }
    
    // 两种情况需要计数:
    // 1. 前一个差值为0(首次出现摆动方向)
    // 2. 当前差值与前一个差值异号(出现交替)
    if (preDiff == 0 || preDiff * curDiff < 0) {
        cnt++;          // 摆动序列长度加1
        preDiff = curDiff;  // 更新前一个差值为当前差值
    }
}

3.4 返回结果

return cnt;
  • 最终 cnt 即为最长摆动子序列的长度。

四、算法原理与案例演示

4.1 关键逻辑解析

  • 为什么用乘积判断异号?
    preDiff * curDiff < 0,说明两者一正一负,满足交替条件。
  • 为什么初始 preDiff = 0
    初始状态下没有前一个差值,当第一个非0差值出现时(curDiff ≠ 0),必然满足 preDiff == 0,此时计数加1,同时将 preDiff 更新为当前差值,为下一次判断做准备。
  • 为什么忽略差值为0的情况?
    平坡(curDiff = 0)不会改变摆动方向,例如 [1,2,2,3] 中,2-2=0 不影响 1-2=-12-3=-1 的方向(非交替,不计数)。

4.2 案例演示

案例1:标准摆动序列

输入:nums = [1,7,4,9,2,5]
步骤解析:

  • i=1curDiff=7-1=6preDiff=0 → 计数 cnt=2preDiff=6
  • i=2curDiff=4-7=-36*(-3)=-18 < 0 → 计数 cnt=3preDiff=-3
  • i=3curDiff=9-4=5-3*5=-15 < 0 → 计数 cnt=4preDiff=5
  • i=4curDiff=2-9=-75*(-7)=-35 < 0 → 计数 cnt=5preDiff=-7
  • i=5curDiff=5-2=3-7*3=-21 < 0 → 计数 cnt=6
  • 结果:6(符合预期)。
案例2:含平坡的序列

输入:nums = [1,2,2,2,3]
步骤解析:

  • i=1curDiff=1preDiff=0cnt=2preDiff=1
  • i=2curDiff=0 → 跳过;
  • i=3curDiff=0 → 跳过;
  • i=4curDiff=11*1=1 > 0(同号,不计数);
  • 结果:2(最长摆动序列为 [1,2][2,3])。
案例3:单调序列

输入:nums = [1,2,3,4,5]
步骤解析:

  • 所有 curDiff 均为正,始终满足 preDiff * curDiff > 0 → 仅初始计数 cnt=1,最终加1次(i=1时);
  • 结果:2(最长摆动序列为任意两个元素,如 [1,2])。

五、算法复杂度分析

  • 时间复杂度O(n),仅需遍历数组一次(n 为数组长度);
  • 空间复杂度O(1),仅使用常数个变量(curDiffpreDiffcnt),与输入规模无关。

六、常见误区与优化说明

6.1 误区1:误认为需要存储子序列元素

实际上,题目只要求返回长度,无需记录具体元素,通过计数即可实现。

6.2 误区2:处理平坡时错误更新差值

curDiff = 0 时,不能更新 preDiff,否则会破坏交替判断(例如将平坡误判为摆动方向)。

6.3 优化点:合并判断条件

代码中 preDiff == 0 || preDiff * curDiff < 0 可合并为 (preDiff <= 0 && curDiff > 0) || (preDiff >= 0 && curDiff < 0),逻辑等价但更直观,本质都是判断方向交替。

七、总结:贪心策略的核心思想

本题通过贪心算法实现高效求解,核心在于:

  1. 聚焦于差值的方向交替,而非具体数值;
  2. 只在出现有效交替时计数,忽略平坡和同方向差值;
  3. 用常数空间和线性时间完成计算,是最优解法。

掌握这种“抓关键特征(极值点/方向交替)”的思路,可解决类似的序列优化问题(如最长递增子序列的贪心解法)。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值