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;
}
};
Name | Value |
---|---|
时间复杂度 | |
空间复杂度 |
贪心
题目要求数字之间的差要正负交替,我们将差的正负描述为上坡、下坡两种情况,遍历序列的时需要记录当前两个元素的差值以及上两个元素的差值
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; // 更新上一次的差值
}
}
};
用贪心的思想,将每一个局部坡度不变化的子序列数量相加,就是最终最长的摇摆子序列的长度
坡度变化会有三种情况
- 坡度相反,记录上一个局部坡度不变化的子序列
result++
,更新prev_diff
- 坡度不变,当前为局部坡度不变的子序列,跳过当前元素
- 坡度变为 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;
}
};
Name | Value |
---|---|
时间复杂度 | |
空间复杂度 |
动态规划
当一个元素满足摆动序列时,这个元素要么上升,要么下降,取决于前一个元素。列出状态表达式:
- 表示以前 个元素中的某一个为结尾的最长上升摆动序列的长度
- 表示以前 个元素中的某一个为结尾的最长下降摆动序列的长度
状态方程为
最终答案为 ,其中 为序列长度
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]);
}
};
Name | Value |
---|---|
时间复杂度 | |
空间复杂度 |
- 创建 , 等于序列长度,初始化默认有一次摆动
- 遍历序列,并同时更新 ,
- 发生上升摆动,最长上升摆动序列长度则为最长下降摆动序列长度
- 发生下降摆动,最长下降摆动序列长度则为最长上升摆动序列长度
- 未发生摆动,维护当前元素为上一次的最大摆动长度即可
- 返回上升摆动序列长度和下降摆动序列长度的最大值即为结果
用一个表格帮助理解,以题目输入的示例二为例
input: nums = [1,17,5,10,13,15,10,5,16,8]
output: 7
i | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | n |
---|---|---|---|---|---|---|---|---|---|---|---|
nums | 1 | 17 | 5 | 10 | 13 | 15 | 10 | 5 | 16 | 8 | |
state | init | ↑ | ↓ | ↑ | ↑ | ↑ | ↓ | ↓ | ↑ | ↓ | |
up | 1 | 2 | 2 | 4 | 4 | 4 | 4 | 4 | 6 | 6 | |
dowm | 1 | 1 | 3 | 3 | 3 | 3 | 5 | 5 | 5 | 7 |
根据此表格可以发现,我们其实可以将 , 简化成两个变量来维护,而不是维护两个数组,节省空间
- 发生上升摆动时
- 发生下降摆动时
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);
}
};
Name | Value |
---|---|
时间复杂度 | |
空间复杂度 |