当前位置: 首页 > article >正文

【贪心、DP、线段树优化】Leetcode 376. 摆动序列

贪心算法:选 “关键转折点”

  1. 初始状态:把数组第一个元素当作起点,此时前一个差值符号设为平坡(即差值为0)。
  2. 遍历数组:从第二个元素开始,依次计算当前元素和前一个元素的差值。
  3. 差值符号判断
    • 差值大于0:要是之前的差值是小于等于0(平坡或者下降状态),那就说明找到了一个从下降到上升的摆动点,更新最大摆动点数,同时把前一个差值符号标记为上升(大于0)。
    • 差值小于0:若之前的差值是大于等于0(平坡或者上升状态),表明找到了一个从上升到下降的摆动点,更新最大摆动点数,并且把前一个差值符号标记为下降(小于0)。
    • 差值等于0:这种情况就是遇到了平坡,直接跳过,前一个差值符号保持不变。
  4. 特殊情况处理:若数组元素数量小于2,直接返回元素数量。
class Solution {
public:int wiggleMaxLength(vector<int>& nums) {if (nums.size() < 2) return nums.size();int preDiff = 0, curDiff = 0;int res = 1; // 至少有一个元素for (int i = 1; i < nums.size(); ++i) {curDiff = nums[i] - nums[i - 1];// 前一个差为0(初始或平坡) 或者 当前差与前一个差符号不同if ((preDiff >= 0 && curDiff < 0) || (preDiff <= 0 && curDiff > 0)) { res++;preDiff = curDiff; // 更新前一个差}}return res;}
};

动态规划:维护 “上升、下降” 状态

up[i]记录的是以nums[i]结尾且最后一步为上升的最长摆动子序列长度,down[i]记录的是以nums[i]结尾且最后一步为下降的最长摆动子序列长度。在遍历数组的过程中,依据当前元素与前一个元素的大小关系,对这两个数组进行动态更新,最终取二者的最大值。

  1. 数组初始化updown数组的所有元素都初始化为1。这是因为每个单独的元素都能构成一个长度为1的摆动序列。
  2. 遍历数组:从第二个元素开始遍历数组,对于每个元素nums[i]
    • 若当前元素大于前一个元素(即nums[i] > nums[i-1]):
      • 对于up[i],由于当前是上升趋势,它的值可以由前一个下降状态转移过来,即up[i] = down[i-1] + 1
      • down[i]保持前一个下降状态的值不变,即down[i] = down[i-1]
    • 若当前元素小于前一个元素(即nums[i] < nums[i-1]):
      • 对于down[i],因为当前是下降趋势,它的值可以由前一个上升状态转移过来,即down[i] = up[i-1] + 1
      • up[i]则保持前一个上升状态的值不变,即up[i] = up[i-1]
    • 若当前元素等于前一个元素(即nums[i] = nums[i-1]):
      • 这种情况下没有形成摆动,所以up[i]down[i]都保持前一个状态的值不变,即up[i] = up[i-1]down[i] = down[i-1]
  3. 结果获取:遍历结束后,updown数组的最后一个元素分别代表以最后一个元素结尾的上升和下降摆动序列的最大长度,取这两个值中的较大值作为最终结果。
class Solution {
public:int wiggleMaxLength(vector<int>& nums) {int n = nums.size();if (n < 2) return n;vector<int> up(n, 1), down(n, 1);for (int i = 1; i < n; ++i) {if (nums[i] > nums[i - 1]) {up[i] = down[i - 1] + 1;down[i] = down[i - 1];} else if (nums[i] < nums[i - 1]) {down[i] = up[i - 1] + 1;up[i] = up[i - 1];} else {up[i] = up[i - 1];down[i] = down[i - 1];}}return max(up[n - 1], down[n - 1]);}
};

线段树优化动态规划

  1. Node结构体
    • up_max:表示区间内以任意位置结尾且最后一步为上升的最长摆动子序列长度。
    • down_max:同理,表示最后一步为下降的最长长度。
  2. SegmentTree类
    • build:递归构建线段树,每个节点维护对应区间的up_maxdown_max
    • queryUp/queryDown:查询区间[0, idx]内的最大up/down值,用于动态规划状态转移。
    • update:更新指定位置的updown值,并递归更新父节点。
  3. Solution类
    • wiggleMaxLength:主函数,使用线段树优化的动态规划计算最长摆动序列长度。
    • 通过线段树快速查询前缀最大值,实现O(nlogn)时间复杂度。
  • 时间复杂度O(nlogn),每次查询和更新操作均为O(logn)
  • 空间复杂度O(n),主要用于存储线段树数组。
#include <vector>
#include <algorithm>
using namespace std;// 线段树节点结构,维护区间内的up和down状态最大值
struct Node {int up_max;     // 区间内以任意位置结尾且最后一步为上升的最长摆动子序列长度int down_max;   // 区间内以任意位置结尾且最后一步为下降的最长摆动子序列长度Node() : up_max(1), down_max(1) {}  // 默认构造函数,初始值为1(单个元素的情况)
};class SegmentTree {
private:vector<Node> tree;  // 线段树数组int n;              // 原始数组大小// 递归构建线段树void build(int node, int start, int end) {if (start == end) {// 叶子节点,初始值为1tree[node] = Node();return;}int mid = (start + end) / 2;build(2*node+1, start, mid);      // 构建左子树build(2*node+2, mid+1, end);      // 构建右子树// 父节点的up_max和down_max取左右子树的最大值tree[node].up_max = max(tree[2*node+1].up_max, tree[2*node+2].up_max);tree[node].down_max = max(tree[2*node+1].down_max, tree[2*node+2].down_max);}// 查询区间[0, idx]内的up_maxint queryUp(int node, int start, int end, int idx) {if (end <= idx) return tree[node].up_max;  // 当前区间完全在查询范围内if (start > idx) return 0;                 // 当前区间完全不在查询范围内int mid = (start + end) / 2;// 递归查询左子树和右子树并取最大值return max(queryUp(2*node+1, start, mid, idx), queryUp(2*node+2, mid+1, end, idx));}// 查询区间[0, idx]内的down_maxint queryDown(int node, int start, int end, int idx) {if (end <= idx) return tree[node].down_max;  // 当前区间完全在查询范围内if (start > idx) return 0;                   // 当前区间完全不在查询范围内int mid = (start + end) / 2;// 递归查询左子树和右子树并取最大值return max(queryDown(2*node+1, start, mid, idx), queryDown(2*node+2, mid+1, end, idx));}// 更新位置pos的up和down值void update(int node, int start, int end, int pos, int up_val, int down_val) {if (start == end) {// 找到目标叶子节点,更新up和down值tree[node].up_max = up_val;tree[node].down_max = down_val;return;}int mid = (start + end) / 2;if (pos <= mid)update(2*node+1, start, mid, pos, up_val, down_val);  // 更新左子树elseupdate(2*node+2, mid+1, end, pos, up_val, down_val);  // 更新右子树// 更新父节点的up_max和down_maxtree[node].up_max = max(tree[2*node+1].up_max, tree[2*node+2].up_max);tree[node].down_max = max(tree[2*node+1].down_max, tree[2*node+2].down_max);}public:// 构造函数,初始化线段树SegmentTree(int size) {n = size;tree.resize(4*n);  // 线段树数组大小通常为4倍原始数组大小build(0, 0, n-1);  // 从根节点开始构建线段树}// 对外接口:查询前idx个位置中的up_maxint queryUp(int idx) {return queryUp(0, 0, n-1, idx);}// 对外接口:查询前idx个位置中的down_maxint queryDown(int idx) {return queryDown(0, 0, n-1, idx);}// 对外接口:更新位置pos的up和down值void update(int pos, int up_val, int down_val) {update(0, 0, n-1, pos, up_val, down_val);}
};class Solution {
public:int wiggleMaxLength(vector<int>& nums) {int n = nums.size();if (n < 2) return n;  // 边界情况处理SegmentTree st(n);  // 初始化线段树// 从第二个元素开始遍历数组for (int i = 1; i < n; ++i) {// 查询前i-1个位置中的up和down最大值int pre_up = st.queryUp(i - 1);int pre_down = st.queryDown(i - 1);int cur_up = 1, cur_down = 1;  // 当前位置的初始值// 根据当前元素与前一个元素的关系更新cur_up和cur_downif (nums[i] > nums[i - 1]) {cur_up = pre_down + 1;  // 上升状态可以由前一个下降状态转移而来cur_down = pre_down;    // 下降状态保持不变} else if (nums[i] < nums[i - 1]) {cur_down = pre_up + 1;  // 下降状态可以由前一个上升状态转移而来cur_up = pre_up;        // 上升状态保持不变} else {// 相等时不形成摆动,保持前一个状态cur_up = pre_up;cur_down = pre_down;}// 更新线段树中当前位置的up和down值st.update(i, cur_up, cur_down);}// 返回整个数组中的最大摆动长度return max(st.queryUp(n - 1), st.queryDown(n - 1));}
};

相关文章:

【贪心、DP、线段树优化】Leetcode 376. 摆动序列

贪心算法&#xff1a;选 “关键转折点” 初始状态&#xff1a;把数组第一个元素当作起点&#xff0c;此时前一个差值符号设为平坡&#xff08;即差值为0&#xff09;。遍历数组&#xff1a;从第二个元素开始&#xff0c;依次计算当前元素和前一个元素的差值。差值符号判断&…...

CppCon 2015 学习:C++ in the audio industry

实时编程&#xff08;real-time programming&#xff09;&#xff1a;音频处理对延迟极度敏感&#xff0c;要求代码必须非常高效且稳定。无锁线程同步&#xff08;lock-free thread synchronization&#xff09;&#xff1a;避免阻塞&#xff0c;提高性能&#xff0c;尤其是在多…...

C++算法-动态规划2

第 4 题 字符串分割 (Word Break) 难度: Medium备注&#xff1a;出自 leetcode题目描述 Given a string s and a dictionary of words dict, determine if s can be segmented into a space-separated sequence of one or more dictionary words. For example, given s "l…...

软信天成:数据驱动型背后的人工智能,基于机器学习的数据管理

在数字化转型浪潮中&#xff0c;当代企业如同逆水行舟&#xff0c;不进则退。无数企业希望通过数字化转型捕获全新的市场机遇&#xff0c;改善财政状况&#xff0c;在未来市场竞争中占据一席之地。要想获得成功的数字化转型&#xff0c;关键因素在于具备可靠、及时的数据用以支…...

MySQL提升

事务 事务&#xff1a;在多个操作合在一起视为一个整体。要么就不做、要么就做完。 事务应该满足ACID A : 原子性。不可分割。C : 一致性。追求的目标&#xff0c;在开始到结束没有发生预定外的情况。I : 隔离性。不同的事务是独立的。D : 持久性。系统崩溃&#xff0c;数据依然…...

hbase资源和数据权限控制

hbase适合大数据量下点查 https://zhuanlan.zhihu.com/p/471133280 HBase支持对User、NameSpace和Table进行请求数和流量配额限制&#xff0c;限制频率可以按sec、min、hour、day 对于请求大小限制示例&#xff08;5K/sec,10M/min等&#xff09;&#xff0c;请求大小限制单位如…...

VMWare下设置共享文件,/mnt/hgfs下却不显示共享文件的解决方法

一、共享文件夹设置步骤 打开虚拟机设置&#xff1a;右键点击虚拟机 → 选择 “设置” → 切换到 “选项” 标签页 → 点击 “共享文件夹”启用共享功能&#xff1a;选择 “总是启用”&#xff08;确保虚拟机已关闭或处于运行状态&#xff09;添加共享文件夹&#xff1a; 点击…...

go语言的锁

本篇文章主要讲锁&#xff0c;主要会涉及go的sync.Mutex和sync.RWMutex。 一.锁的概念和发展 1.1 锁的概念 所谓的加锁和解锁其实就是指一个数据是否被占用了&#xff0c;通过Mutex内的一个状态来表示。 例如&#xff0c;取 0 表示未加锁&#xff0c;1 表示已加锁&#xff…...

C++11完美转发

在 C11 之前&#xff0c;泛型函数在传递参数时无法保证参数的原始类型&#xff08;左值或右值&#xff09;导致额外的拷贝或移动操作&#xff0c;完美转发是一种高效传递技术&#xff0c;能够保持参数的原始特性&#xff0c;避免额外的性能开销 完美转发是指在泛型编程中以参数…...

VUE解决页面请求接口大规模并发的问题(请求队列)

方案1&#xff1a; 请求队列 // RequestQueue.js export default class RequestQueue {constructor(maxConcurrent) {this.maxConcurrent maxConcurrent; // 最大并发请求数this.currentConcurrent 0; // 当前并发请求数this.queue []; // 请求队列this.requestId 0; // …...

IDEA安装迁移IDEA配置数据位置

需求 因为C盘有清空风险&#xff0c;需要把IDEA&#xff08;2025&#xff09;安装位置以及配置数据都挪到D盘。 安装 到官网下载安装包 安装&#xff0c;这里可以改下安装位置 这几个选项随意&#xff0c;然后一直下一步就好 完成后重启或不重启都随意 迁移数据 初次安…...

Blazor-表单提交的艺术:如何优雅地实现 (下)

在上一章节中我们使用HTML的方式介绍了如何在Blazor框架下进行表单的提交&#xff0c;而在Blazor框架中也为我们内置了<EditForm>组件来代替原始的HTML,<form>&#xff0c;下面我们将对<EditForm>的用法进行讲解&#xff0c;并将两种表单方式进行对比&#x…...

五子棋网络对战游戏的设计与实现设计与实现【源码+文档】

五子棋网络对战游戏的设计与实现 摘 要 在现代社会中,及其它无线设备越来越多的走进普通老百姓的工作和生活。随着3G技术的普及与应用&#xff0c;基于Java开发的软件在上的使用非常的广泛&#xff0c;增值服务的内容也是越来越多&#xff0c;对丰富人们的生活内容、提供快…...

Vue基础(14)_列表过滤、列表排序

Array.prototype.filter()【ES5】 filter() 方法创建给定数组一部分的浅拷贝&#xff0c;其包含通过所提供函数实现的测试的所有元素。 语法&#xff1a; filter(callbackFn) filter(callbackFn, thisArg) 参数&#xff1a; callbackFn(回调函数)&#xff1a;为数组中的每个元…...

Spring Boot项目中JSON解析库的深度解析与应用实践

在现代Web开发中&#xff0c;JSON&#xff08;JavaScript Object Notation&#xff09;作为轻量级的数据交换格式&#xff0c;已成为前后端通信的核心桥梁。Spring Boot作为Java生态中最流行的微服务框架&#xff0c;提供了对多种JSON库的无缝集成支持。本文将深入探讨Spring B…...

我用Amazon Q写了一个Docker客户端,并上架了懒猫微服商店

自从接触了Amazon Q&#xff0c;我陆陆续续写了不少小软件&#xff0c;其中这个项目是一个典型的例子&#xff0c;自己平时来使用&#xff0c;也分享给一些 NAS 爱好者来用。 故事还要用上次折腾黑群晖说起&#xff0c;本意想把 NAS 和打印机共享二合一的&#xff0c;所以把闲着…...

Django CMS 的 Demo

以下是关于 Django CMS 的 Demo 示例及相关资源的整理 安装与运行 Django CMS 示例 使用 djangocms-installer 快速创建 Django CMS 项目&#xff1a; pip install django_cms djangocms -p . mysite安装记录 pip install django-cms Looking in indexes: https://pypi.tun…...

在 UE5 蓝图中配置Actor类型的Asset以作为位置和旋转设置目标

目标 UE5的蓝图的事件图表里面&#xff0c;有一个模块&#xff08;节点&#xff09;如图&#xff0c;这是一个设置Actor的location和rotation量的模块&#xff0c;其中需要接收一个Target作为输入&#xff0c;这个Target应该就是一个在map中具备location和rotation信息的实例化…...

Android 之 kotlin 语言学习笔记四(Android KTX)

一、Android KTX 简介 Android KTX 是包含在 Android Jetpack 及其他 Android 库中的一组 Kotlin 扩展程序。KTX 扩展程序可以为 Jetpack、Android 平台及其他 API 提供简洁的惯用 Kotlin 代码。为此&#xff0c;这些扩展程序利用了多种 Kotlin 语言功能&#xff0c;其中包括&…...

适用于vue3的大屏数据展示组件库DataV(踩坑版)

踩坑版 如果按照官网(https://datav-vue3.jiaminghi.com/)的vue3安装有问题 官网是将dataview/datav-vue3 安装为本地依赖 npm install dataview/datav-vue31、跑起来报错&#xff08;报错信息忘记保留了&#xff09; 有人说找到node_modules&#xff0c; 安装成功后会有这个…...

mysql实现分页查询

文章目录 mysql实现分页查询1. 使用LIMIT和OFFSET2. 使用计算OFFSET的函数&#xff08;适用于动态分页&#xff09;3. 使用MySQL的变量&#xff08;适用于存储过程&#xff09; 获取所有用户数据并分页 mysql实现分页查询 在MySQL中实现分页查询&#xff0c;通常我们会使用LIM…...

Flink checkpoint

对齐检查点 (Aligned Checkpoint) Flink 的分布式快照机制受到 Chandy-Lamport 算法的启发。 其核心元素是数据流中的屏障&#xff08;Barrier&#xff09;。 Barrier 注入 &#xff1a;JobManager 中的 Checkpoint Coordinator 指示 Source 任务开始 Checkpoint。Source 任务…...

【java】在springboot中实现证书双向验证

证书生成 public static void main(String[] args) throws Exception {// 生成密钥对KeyPairGenerator keyPairGenerator KeyPairGenerator.getInstance("RSA");keyPairGenerator.initialize(2048);KeyPair keyPair keyPairGenerator.generateKeyPair();// 获取私…...

CppCon 2015 学习:Functional Design Explained

这两个 C 程序 不完全相同。它们的差异在于对 std::cout 的使用和代码格式。 程序 1&#xff1a; #include <iostream> int main(int argc, char** argv) {std::cout << "Hello World\n"; }解释&#xff1a;这个程序是 正确的。std::cout 是 C 标准库中…...

基于3D对象体积与直径特征的筛选

1&#xff0c;目的 筛选出目标3D对象。 效果如下&#xff1a; 2&#xff0c;原理 使用3D对象的体积与直径特征进行筛选。 3&#xff0c;代码解析 3.1&#xff0c;预处理2.5D深度图。 * 参考案例库&#xff1a;select_object_model_3d.hdev * ****************************…...

GIT - 如何从某个分支的 commit创建一个新的分支?

如果上一个Release 分支被污染了&#xff0c;想要还原这个分支最原始的样子&#xff0c;有什么办法或者说该怎么办呢&#xff1f;简单来说&#xff0c;就是如何从某个指定的 commit 创建一个新的 Git 分支&#xff1f; 操作非常简单&#xff01; 命令格式 git branch <ne…...

Claude vs ChatGPT vs Gemini:功能对比、使用体验、适合人群

随着AI应用全面进入生产力场景&#xff0c;市面上的主流AI对话工具也进入“三国杀”时代&#xff1a; Claude&#xff08;Anthropic&#xff09;&#xff1a;新锐崛起&#xff0c;语言逻辑惊艳&#xff0c;Opus 模型被称为 GPT-4 杀手ChatGPT&#xff08;OpenAI&#xff09;&a…...

线程基础编程

早期的计算机只能执行一个任务&#xff0c;一旦任务完成&#xff0c;计算机就会等待下一个任务。这种模型效率低下&#xff0c;无 法充分利用计算机的性能。 随着计算机技术的发展&#xff0c;操作系统开始支持多进程模型&#xff0c;即同时执行多个任务。每个任务被称为一个进…...

DJango项目

一.项目创建 在想要将项目创键的目录下,输入cmd (进入命令提示符)在cmd中输入:Django-admin startproject 项目名称 (创建项目)cd 项目名称 (进入项目)Django-admin startapp 程序名称 (创建程序)python manage.py runserver 8080 (运行程序)将弹出的网址复制到浏览器中…...

深入了解JavaScript当中如何确定值的类型

JavaScript是一种弱类型语言&#xff0c;当你给一个变量赋了一个值&#xff0c;该值是什么类型的&#xff0c;那么该变量就是什么类型的&#xff0c;并且你还可以给一个变量赋多种类型的值&#xff0c;也不会报错&#xff0c;这就是JavaScript的内部机制所决定的&#xff0c;那…...