贪心算法专题(二):波动中的智慧——只取极值「摆动序列」

哈喽各位,我是前端小L。

欢迎来到贪心算法专题第二篇! 什么是"摆动"?简单说就是一上一下 。比如 [1, 7, 4, 9, 2, 5],差值是 +6, -3, +5, -7, +3,正负交替,这就是摆动序列。 而 [1, 4, 7, 9] 单调递增,或者 [1, 1, 1] 平的,都不是摆动。

我们的任务是:给你一个数组,你可以随意删除 里面的元素(也就是求子序列),剩下的元素必须构成一个摆动序列。请问最长能剩多少个?

力扣 376. 摆动序列

https://leetcode.cn/problems/wiggle-subsequence/

题目分析:

  • 输入 :整数数组 nums

  • 目标:最长摆动子序列的长度。

  • 例子[1, 17, 5, 10, 13, 15, 10, 5, 16, 8]

    • 整个数组显然不是摆动的(比如 10, 13, 15 连着涨)。

    • 我们可以删掉 13,保留 10, 15,就变成了 ...10, 15, 10...(一上一下)。

    • 实际上,我们只需要保留所有的"峰"和"谷",删掉所有在半山腰上的点。

核心思维:忽略"平坡",只数"峰谷"

想象把你手里的数组画成一张折线图。

  • 摆动 的本质就是折线的拐点

  • 如果连续上升 1 -> 2 -> 3 -> 4,这是一条直线上坡。为了构成摆动,我们只需要保留起点 1终点 4。中间的 23 都可以删掉,因为它们没有改变趋势。

贪心策略: 我们只需要统计数组中**"峰"(Peak)"谷"(Valley)**的数量。

  • :数值先升后降。即 preDiff > 0curDiff < 0

  • :数值先降后升。即 preDiff < 0curDiff > 0

  • 平坡处理 :这是难点!比如 1 -> 2 -> 2 -> 2 -> 3。这种平坡应该被视为单调的一部分,不计入摆动,除非平坡之后方向变了。

算法流程

  1. 初始化

    • curDiff:当前数与后一个数的差值。

    • preDiff:前一对数的差值。

    • result:记录峰谷个数。默认序列最少有一个元素(除非空数组),所以初始为 1(默认把最右边的那个端点算上)。

  2. 遍历数组 :从 0nums.size() - 2(计算 nums[i]nums[i+1] 的差)。

  3. 判断拐点

    • 如果 (preDiff <= 0 && curDiff > 0) ------ 出现

    • 或者 (preDiff >= 0 && curDiff < 0) ------ 出现

    • 注意 :这里的 = 是为了处理由平坡变成上下坡的情况(如 1-1-2,中间的 1 算谷底)。

  4. 更新状态

    • result++

    • preDiff = curDiff关键点 :只有在出现摆动变化的时候,才更新 preDiff。这样可以自动过滤掉单调区间内的平坡。

代码实现 (C++)

C++

复制代码
#include <vector>

using namespace std;

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        if (nums.size() <= 1) return nums.size();

        int curDiff = 0; // 当前一对元素的差值
        int preDiff = 0; // 前一对元素的差值
        int result = 1;  // 记录峰值个数,默认最后一个元素算一个峰值

        for (int i = 0; i < nums.size() - 1; i++) {
            curDiff = nums[i + 1] - nums[i];

            // 出现峰或谷
            // preDiff <= 0 && curDiff > 0 : 之前是平或降,现在升了(谷)
            // preDiff >= 0 && curDiff < 0 : 之前是平或升,现在降了(峰)
            if ((preDiff <= 0 && curDiff > 0) || (preDiff >= 0 && curDiff < 0)) {
                result++;
                
                // 关键细节:只在摆动变化的时候更新 preDiff
                // 这样能处理单调区间中有平坡的情况,如 1->2->2->2->3
                // preDiff 会一直保持为正,直到遇到下降
                preDiff = curDiff;
            }
        }

        return result;
    }
};

深度复杂度分析

  • 时间复杂度:O(N)

    • 我们只需要遍历一次数组。
  • 空间复杂度:O(1)

    • 只需要几个变量记录差值和结果。

    • 相比之下,动态规划解法通常需要两个数组 up[N]down[N],空间复杂度为 O(N)(虽然可以优化到 O(1),但逻辑比贪心复杂)。

总结:贪心的"视觉化"

今天这道题,如果只看数字,很容易被"平坡"、"删除元素"绕晕。 但如果把它想象成**"山脉图"**,贪心策略就显而易见了:我们只想要山顶和谷底,山腰上的石头全扔掉!

这就是贪心算法的魅力------通过忽略中间过程(单调区间),直接抓住关键变化(转折点)

下一题预告 : 如果我们在一个数组中寻找**"和最大"的连续子数组**(最大子序和),贪心算法该如何操作? 策略很简单:如果当前的累加和变成了负数,它对未来不仅没有贡献,还是个累赘,那我们就果断抛弃它,从头开始算!

下期见!

相关推荐
_OP_CHEN10 小时前
【算法基础篇】(五十七)线性代数之矩阵乘法从入门到实战:手撕模板 + 真题详解
线性代数·算法·矩阵·蓝桥杯·c/c++·矩阵乘法·acm/icpc
天天爱吃肉821810 小时前
【跨界封神|周杰伦×王传福(陶晶莹主持):音乐创作与新能源NVH测试,底层逻辑竟完全同源!(新人必看入行指南)】
python·嵌入式硬件·算法·汽车
im_AMBER10 小时前
Leetcode 114 链表中的下一个更大节点 | 删除排序链表中的重复元素 II
算法·leetcode
xhbaitxl10 小时前
算法学习day38-动态规划
学习·算法·动态规划
多恩Stone10 小时前
【3D AICG 系列-6】OmniPart 训练流程梳理
人工智能·pytorch·算法·3d·aigc
历程里程碑10 小时前
普通数组----轮转数组
java·数据结构·c++·算法·spring·leetcode·eclipse
pp起床10 小时前
贪心算法 | part02
算法·leetcode·贪心算法
sin_hielo10 小时前
leetcode 1653
数据结构·算法·leetcode
2501_9011478310 小时前
面试必看:优势洗牌
笔记·学习·算法·面试·职场和发展
李日灐10 小时前
C++进阶必备:红黑树从 0 到 1: 手撕底层,带你搞懂平衡二叉树的平衡逻辑与黑高检验
开发语言·数据结构·c++·后端·面试·红黑树·自平衡二叉搜索树