跳到主要内容

LeetCode-376 摆动序列

题目描述

来源:力扣(LeetCode) 链接:https://leetcode.cn/problems/wiggle-subsequence

如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 摆动序列 。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。

例如, [1, 7, 4, 9, 2, 5] 是一个 摆动序列 ,因为差值 (6, -3, 5, -7, 3) 是正负交替出现的。

相反,[1, 4, 7, 2, 5] 和 [1, 7, 4, 5, 5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。 子序列 可以通过从原始序列中删除一些(也可以不删除)元素来获得,剩下的元素保持其原始顺序。

给你一个整数数组 nums ,返回 nums 中作为 摆动序列 的 最长子序列的长度 。

题解

枚举状态

可以创建一个枚举类型,用来标记目前的状态时上升还是下降,当元素值上升或下降时改变标记状态,在标记状态改变时记录摆动序列的长度 + 1

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

int result = 1;
enum Up__Down {
UP,
DOWN,
INIT
};

Up__Down state = Up__Down::INIT;
for(int i=1; i < nums.size(); ++i){
if(nums[i] > nums[i-1] && state != Up__Down::UP){
result ++;
state = Up__Down::UP;
}
if(nums[i] < nums[i-1] && state != Up__Down::DOWN){
result ++;
state = Up__Down::DOWN;
}
}
return result;
}
};
NameValue
时间复杂度O(n)O(n)
空间复杂度O(1)O(1)

贪心

题目要求数字之间的差要正负交替,我们将差的正负描述为上坡、下坡两种情况,遍历序列的时需要记录当前两个元素的差值以及上两个元素的差值

class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
int curr_diff = 0; // 当前一对元素差值
int prev_diff = 0; // 上一对元素差值

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

prev_diff = curr_diff; // 更新上一次的差值
}
}
};

用贪心的思想,将每一个局部坡度不变化的子序列数量相加,就是最终最长的摇摆子序列的长度

坡度变化会有三种情况

  1. 坡度相反,记录上一个局部坡度不变化的子序列 result++ ,更新 prev_diff
  2. 坡度不变,当前为局部坡度不变的子序列,跳过当前元素
  3. 坡度变为 0 (当前两个元素差值为 0),局部坡度没有发生摆动,跳过当前元素
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
int curr_diff = 0; // 当前一对元素差值
int prev_diff = 0; // 上一对元素差值
int result = 1; // 记录坡度不变的子序列数量,默认最右有一个

for (int i = 0; i< nums.size()-1; ++i) {
curr_diff = nums[i+1] - nums[i];
// 坡度反向
if ((prev_diff <= 0 && curr_diff > 0) || (prev_diff >= 0 && curr_diff < 0)) {
result++;
prev_diff = curr_diff;
}
}
return result;
}
};

最后加上特殊情况的处理

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

int curr_diff = 0; // 当前一对元素差值
int prev_diff = 0; // 上一对元素差值
int result = 1; // 记录坡度不变的子序列数量,默认最右有一个

for (int i = 0; i< nums.size()-1; ++i) {
curr_diff = nums[i+1] - nums[i];
// 坡度反向
if ((prev_diff <= 0 && curr_diff > 0) || (prev_diff >= 0 && curr_diff < 0)) {
result++;
prev_diff = curr_diff;
}
}
return result;
}
};
NameValue
时间复杂度O(n)O(n)
空间复杂度O(1)O(1)

动态规划

当一个元素满足摆动序列时,这个元素要么上升,要么下降,取决于前一个元素。列出状态表达式:

  • up[i]up[i] 表示以前 ii 个元素中的某一个为结尾的最长上升摆动序列的长度
  • down[i]down[i] 表示以前 ii 个元素中的某一个为结尾的最长下降摆动序列的长度

状态方程为

up[i]={up[i1],nums[i]nums[i1]max(up[i1],down[i1]+1),nums[i]>nums[i1]up[i]=\left\{ \begin{aligned} &up[i-1], & nums[i] \leq nums[i−1]\\ &max(up[i-1], down[i-1] + 1), & nums[i]>nums[i−1] \end{aligned} \right.
down[i]={down[i1],nums[i]nums[i1]max(up[i1]+1,down[i1]),nums[i]<nums[i1]down[i]=\left\{ \begin{aligned} &down[i-1], & nums[i] \geq nums[i−1]\\ &max(up[i-1] + 1, down[i-1]), & nums[i]<nums[i−1] \end{aligned} \right.

最终答案为 max(up[n1],down[n1])max(up[n-1],down[n-1]),其中 nn 为序列长度

class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
int n = nums.size();
if (n < 2) {
return n;
}
vector<int> up(n), down(n);
up[0] = down[0] = 1;
for (int i = 1; i < n; i++) {
if (nums[i] > nums[i - 1]) {
up[i] = max(up[i - 1], down[i - 1] + 1);
down[i] = down[i - 1];
} else if (nums[i] < nums[i - 1]) {
up[i] = up[i - 1];
down[i] = max(up[i - 1] + 1, down[i - 1]);
} else {
up[i] = up[i - 1];
down[i] = down[i - 1];
}
}
return max(up[n - 1], down[n - 1]);
}
};
NameValue
时间复杂度O(n)O(n)
空间复杂度O(n)O(n)
  • 创建 upupdowndown 等于序列长度,初始化默认有一次摆动
  • 遍历序列,并同时更新 upupdowndown
    1. nums[i]>nums[i1]nums[i] > nums[i - 1] 发生上升摆动,最长上升摆动序列长度则为最长下降摆动序列长度 up[i]=down[i1]+1up[i] = down[i-1] + 1
    2. nums[i]<nums[i1]nums[i] < nums[i - 1] 发生下降摆动,最长下降摆动序列长度则为最长上升摆动序列长度 down[i]=up[i1]+1down[i] = up[i - 1] + 1
    3. nums[i]=nums[i1]nums[i] = nums[i - 1] 未发生摆动,维护当前元素为上一次的最大摆动长度即可
  • 返回上升摆动序列长度和下降摆动序列长度的最大值即为结果

用一个表格帮助理解,以题目输入的示例二为例

input: nums = [1,17,5,10,13,15,10,5,16,8]

output: 7

i0123456789n
nums1175101315105168
stateinit
up1224444466
dowm1133335557

根据此表格可以发现,我们其实可以将 upupdowndown 简化成两个变量来维护,而不是维护两个数组,节省空间

  • 发生上升摆动时 up=down+1up = down + 1
  • 发生下降摆动时 down=up+1down = up + 1
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
if(nums.size() < 2) {
return nums.size();
}
int up =1, down = 1;
for(int i = 1; i < nums.size(); ++i) {
if(nums[i] > nums[i-1]) {
up = down + 1;
}
else if(nums[i] < nums[i-1]) {
down = up + 1;
}
}
return max(up, down);
}
};
NameValue
时间复杂度O(n)O(n)
空间复杂度O(1)O(1)