前言:最开始写的时候并没有写出来,代码随想录提供的答案也很令人费解,个人感觉很不直观,也很不好理解,思路并不连贯;我根据代码随想录的启发提供另一种更好理解的思路;
代码随想录解法:
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
//剪枝:
if(nums.size() == 0) return 0;
if(nums.size() == 1) return 1;
int curDiff = 0; //当前点的前方差值
int preDiff = 0; //当前点的后方差值
int result = 1; //记录峰值个数(默认序列结尾处有一个峰值)
for (int i = 0; i < nums.size() - 1; i++) {
curDiff = nums[i + 1] - nums[i];
//出现峰值:
if ((preDiff <= 0 && curDiff > 0) || (preDiff >= 0 && curDiff < 0)) {
result++;
preDiff = curDiff; //注意这里,只在出现峰值的时候更新prediff
}
}
return result;
}
};
我的解法:
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
//剪枝:
if(nums.size() == 0) return 0;
if(nums.size() == 1) return 1;
if(nums.size() == 2 && nums[0] == nums[1]) return 1;
if(nums.size() == 2 && nums[0] != nums[1]) return 2;
int ptr = 1;
while(ptr < nums.size() - 1) {
if(nums[ptr] == nums[ptr - 1] || nums[ptr] == nums[ptr + 1]) //存在想等元素
nums.erase(nums.begin() + ptr);
else if(nums[ptr] > nums[ptr - 1] && nums[ptr + 1] > nums[ptr]) //单调减
nums.erase(nums.begin() + ptr);
else if(nums[ptr] < nums[ptr - 1] && nums[ptr + 1] < nums[ptr]) //单调增
nums.erase(nums.begin() + ptr);
else ptr++;
}
if(nums.size() == 2 && nums[0] == nums[1]) return 1; //排除特殊情况:序列中所有的元素均相同
return nums.size();
}
};
思路1:针对题目,我一般都是先对应着一般的情况去写,然后再补充特殊情况;根据图片可知,如果我们想得到最长的摇摆子序列,那必须保留所有的摆动峰,因此单调增/单调减/保持不变的所有值,都是多余的,都可以直接删掉而不影响摆动峰的数目;删除所有的多余值以后,nums的长度就是子序列的最长长度;
a
a
a
思路2:我们先不考虑针对nums的开头位置/结尾位置的处理,我们先写程序解决对nums中央部位的元素的处理;对所有可能的9种情况(先增 / 先降 / 先平)进行分析,将所有不可能进入子序列的节点全部删除;
- 其中一边有相同元素,则删除当前节点
- 元素位于单调增的中间,删除元素
- 元素位于单调减的中间,删除元素
写出主体代码:在写的时候注意while循环体的判定条件,因为我们在主程序中对ptr指向位置进行判定的时候,需要ptr两边的元素,所以我们为了在结尾位置能对应上,需要保留一个元素位;
int ptr = 1;
while(ptr < nums.size() - 1) {
if(nums[ptr] == nums[ptr - 1] || nums[ptr] == nums[ptr + 1]) //存在想等元素
nums.erase(nums.begin() + ptr);
else if(nums[ptr] > nums[ptr - 1] && nums[ptr + 1] > nums[ptr]) //单调减
nums.erase(nums.begin() + ptr);
else if(nums[ptr] < nums[ptr - 1] && nums[ptr + 1] < nums[ptr]) //单调增
nums.erase(nums.begin() + ptr);
else ptr++;
}
a
a
a
思路3:考虑nums开头部分的节点;我们可以发现我们对数组nums中间部分的节点的处理方式,和对nums开头位置的节点的处理方式是一样的,所以主体代码可以不用改动;
a
a
a
思路4:考虑对nums尾部的节点的处理方式;以“先增”情况为例:
分析:红色位置是当前循环体中ptr指向的位置,而ptr所指向的节点的前一个节点(绿色节点)的前方,一定是下降趋势;因为当ptr到达当前红色位置的时候,代表他前方所有的节点都已经被遍历并被处理过了,所以ptr的前方不存在对摆动毫无作用的点,所以当绿点到红点的趋势为增时,代表绿点前的趋势一定为降,绝不可能是单增/单降/单平;
分析:当ptr处理完当前红色节点后,就会跳出循环体,而留下的紫色节点并不需要进行判断,他们一定是符合摆动的节点,一定可以被纳入到子序列中;
分析:那么同理“先降”情况也是相同的分析过程;
分析:但是“先平”却存在特殊情况;当ptr指向绿色位置的时候就会删除这些绿色节点,这些绿色节点的前面的趋势可能是上升/下降(不可能是平),再指向红色节点对红色节点进行处理,第一种和第三种情况都是符合要求的;但是对于第二种情况,当结尾部分的节点都是平,那么当ptr指向红色位置的时候会删除红色节点,然后跳出while循环;但是此时留下的紫色节点却和前方的节点是“平”的关系。没有被删除掉;所以我们在跳出while循环后仍然要对nums的尾部进行判断;
加入代码:
if(nums.size() == 2 && nums[0] == nums[1]) return 1; //排除特殊情况:序列中所有的元素均相同
return nums.size();