一、问题定义与核心特征
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,同时更新前一个差值。具体规则:
- 若当前差值与前一个差值异号(乘积 < 0),说明出现摆动,计数加1;
- 若前一个差值为0(初始状态或平坡后),当前差值非0,说明首次出现摆动方向,计数加1;
- 差值为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=-1和2-3=-1的方向(非交替,不计数)。
4.2 案例演示
案例1:标准摆动序列
输入:nums = [1,7,4,9,2,5]
步骤解析:
i=1:curDiff=7-1=6,preDiff=0→ 计数cnt=2,preDiff=6;i=2:curDiff=4-7=-3,6*(-3)=-18 < 0→ 计数cnt=3,preDiff=-3;i=3:curDiff=9-4=5,-3*5=-15 < 0→ 计数cnt=4,preDiff=5;i=4:curDiff=2-9=-7,5*(-7)=-35 < 0→ 计数cnt=5,preDiff=-7;i=5:curDiff=5-2=3,-7*3=-21 < 0→ 计数cnt=6;- 结果:
6(符合预期)。
案例2:含平坡的序列
输入:nums = [1,2,2,2,3]
步骤解析:
i=1:curDiff=1,preDiff=0→cnt=2,preDiff=1;i=2:curDiff=0→ 跳过;i=3:curDiff=0→ 跳过;i=4:curDiff=1,1*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),仅使用常数个变量(curDiff、preDiff、cnt),与输入规模无关。
六、常见误区与优化说明
6.1 误区1:误认为需要存储子序列元素
实际上,题目只要求返回长度,无需记录具体元素,通过计数即可实现。
6.2 误区2:处理平坡时错误更新差值
当 curDiff = 0 时,不能更新 preDiff,否则会破坏交替判断(例如将平坡误判为摆动方向)。
6.3 优化点:合并判断条件
代码中 preDiff == 0 || preDiff * curDiff < 0 可合并为 (preDiff <= 0 && curDiff > 0) || (preDiff >= 0 && curDiff < 0),逻辑等价但更直观,本质都是判断方向交替。
七、总结:贪心策略的核心思想
本题通过贪心算法实现高效求解,核心在于:
- 聚焦于差值的方向交替,而非具体数值;
- 只在出现有效交替时计数,忽略平坡和同方向差值;
- 用常数空间和线性时间完成计算,是最优解法。
掌握这种“抓关键特征(极值点/方向交替)”的思路,可解决类似的序列优化问题(如最长递增子序列的贪心解法)。
1093

被折叠的 条评论
为什么被折叠?



