力扣 376. 摆动序列 精讲
力扣 376. 摆动序列 精讲
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 摆动序列 。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。
-
例如, [1, 7, 4, 9, 2, 5] 是一个 摆动序列 ,因为差值 (6, -3, 5, -7, 3) 是正负交替出现的。
-
相反,[1, 4, 7, 2, 5] 和 [1, 7, 4, 5, 5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。
子序列 可以通过从原始序列中删除一些(也可以不删除)元素来获得,剩下的元素保持其原始顺序。
给你一个整数数组 nums ,返回 nums 中作为 摆动序列 的 最长子序列的长度 。
示例 1:
输入:nums = [1,7,4,9,2,5]
输出:6
解释:整个序列均为摆动序列,各元素之间的差值为 (6, -3, 5, -7, 3) 。
示例 2:
输入:nums = [1,17,5,10,13,15,10,5,16,8]
输出:7
解释:这个序列包含几个长度为 7 摆动序列。
其中一个是 [1, 17, 10, 13, 10, 16, 8] ,各元素之间的差值为 (16, -7, 3, -3, 6, -8) 。
示例 3:
输入:nums = [1,2,3,4,5,6,7,8,9]
输出:2
提示:
1 <= nums.length <= 1000
0 <= nums[i] <= 1000
方法一:贪心算法
可以理解为我只要这个数组中的波峰和波谷 即为总长度 且这俩交替出现就行 可以贪心的去省略中间上升到波峰的趋势的那些数字,也可以贪心的去省略下降到波谷的趋势。
具体的可以看注释,还看不懂就看这个。
class Solution {
public int wiggleMaxLength(int[] nums) {
if(nums.length<=1){
return nums.length;
}
//贪心算法
//结果是初始的时候就算上了最左侧的 然后自增的时候就是俩了
int res = 1;
//记录上一次加入到序列中的节点的和后一个节点的差值
int pre = 0;
//记录当前节点和后一个节点的差值
int cur=0;
for(int i=0;i<nums.length-1;i++){
//去看每一个峰值
//就算满足了条件 也会一直更新 即找到局部最大或最小的去更新
cur = nums[i+1]-nums[i];
//每次局部峰值只会++一次 你可以理解为峰值(pre和cur正负不一致)就记录
if(cur>0&&pre<=0||cur<0&&pre>=0){
res++;
pre=cur;
}
}
return res;
}
}
方法二:dp 时间O(N2) 空间O(N)
具体的可以看注释,还看不懂就看这个。
class Solution {
public int wiggleMaxLength(int[] nums) {
int n=nums.length;
if(n<=1){
return n;
}
//dp算法
//关键就在于从前面的序列转移过来的方式
//而官方题解给出了O(n)的方式 感觉证明我不太理解
//这里代码随想录只给出了O(N方)的dp
//定义一个最后结尾为峰(即最后两个数是上升的序列)的dp数组
//up[i]代表从0~i组成的最长的子序列长度且序列最后两个数是递增的
int[] up = new int[n];
//定义一个最后结尾为谷(即最后两个数是下降的序列)的dp数组
//down[i]代表从0~i组成的最长的子序列长度且序列最后两个数是递减的
int[] down = new int[n];
//初始值都是1
up[0]=1;
down[0]=1;
//遍历nums
for(int i=1;i<n;i++){
//对于每一个值都要和前面所有的值比较 得到最大长度的可能性
for(int j=0;j<i;j++){
if(nums[i]>nums[j]){
//当当前值大于序列中的值时 长度只可能从down[j]中增加
//即将nums[i]作为峰跟在nums[j]这个谷后面
up[i] = Math.max(up[i],down[j]+1);
//我虽然没你大 但是我作为个更长的序列 我应当保存最大的值
//因此这个更新没啥意义。。。
//从下面就更新好了
// down[i] = Math.max(down[i],down[j]);
}else if(nums[i]<nums[j]){
//当当前值小于序列中的值时 长度只可能从up[j]中增加
//即将nums[i]作为谷跟在nums[j]这个峰后面
down[i] = Math.max(down[i],up[j]+1);
//我虽然没你大 但是我作为个更长的序列 我应当保存最大的值
//因此这个更新没啥意义。。。
//从上面就更新好了
// up[i] = Math.max(up[i],up[j]);
}else{
up[i] = Math.max(up[i],up[j]);
down[i] = Math.max(down[i],down[j]);
}
}
}
//因为最后一个值要么是波峰 要么是波谷 一定在最后一个位置上
return Math.max(up[n-1],down[n-1]);
}
}
方法三:dp 时间O(N) 空间O(N)
注意 dp数组的定义变了哦~
具体的可以看注释,还看不懂就看这个。
class Solution {
public int wiggleMaxLength(int[] nums) {
int n=nums.length;
if(n<=1){
return n;
}
//dp算法
//而官方题解给出了O(n)的方式 差不多看懂了
//关键在于当只通过和前一个值进行比较 就能得出前面一定是递减到一个谷这个结论
//up[i]代表从0~i中某一个数为结尾的最长摆动序列的长度 且最后为递增的两个数
int[] up = new int[n];
//down[i]代表从0~i中某一个数为结尾的最长摆动序列的长度 且最后为递减的两个
int[] down = new int[n];
//初始值都是1
up[0]=1;
down[0]=1;
//遍历nums
for(int i=1;i<n;i++){
//对于每一个值都要和前面所有的值比较
if(nums[i]<nums[i-1]){
//当当前值小于前一个值时
//对于up[i] 结尾处是递增的 而当前值小于最高处 因此0~i中的最长上摆序列依旧不会变 因为对于谷而言 i-1和i都一样(up[i-1]一致)
up[i] = up[i-1];
//对于down[i] 因为当前值小于前一个 此处一定是一个下降的趋势 前面一定有一个峰 那么我有可能是已经在递减的路上了(和down[i-1]一致 那么这种时候down[i-1]一定是最长的) 也有可能是第一个递减的数字(从up[i-1]+1来 代表从上摆序列到下摆序列的过程)
down[i] = Math.max(down[i-1],up[i-1]+1);
}else if((nums[i]>nums[i-1])){
//当当前值大于前一个值时
//对于up[i] 结尾处是递增的 而当前值大于最高处因此可以从up[i-1]来,也可以从down[i-1]+1来 因为我大于前面一个值 因此前面的数组一定是一个递增的过程,那么前面第一个一定是谷而不是峰,那么我可以从前面的谷+1 从下摆转到上摆序列来
up[i] = Math.max(up[i-1],down[i-1]+1);
//对于down[i] 我大于前一个值 那么对于总体的0~i而言 我肯定去nums[i-1]位置上的值更好 因为我要的是下摆序列 结尾一定要下降的 因此还是down[i-1]
down[i]=down[i-1];
}else{
//相等的时候 直接和上一个一样
up[i] = up[i-1];
down[i] = down[i-1];
}
}
//最长序列一定在两个数组最后
return Math.max(up[n-1],down[n-1]);
}
}
方法四:dp 时间O(N) 空间O(1)
空间优化 没啥技术含量
具体的可以看注释,还看不懂就看这个。
class Solution {
public int wiggleMaxLength(int[] nums) {
int n=nums.length;
if(n<=1){
return n;
}
//dp算法
//优化了空间
//如果你又看到这个题了 一定要看看上个版本的详细注释
//注释写的很清楚了捏 这个是时间O(n) 空间O(1)的算法
//上个是时间O(n) 空间O(n)
//代码随想录的是时间O(n方) 空间O(n)
int up = 1;
int down = 1;
for(int i=1;i<n;i++){
if(nums[i]<nums[i-1]){
down = Math.max(down,up+1);
}else if((nums[i]>nums[i-1])){
up = Math.max(up,down+1);
}else{
}
}
//最长序列一定在两个数组最后
return Math.max(up,down);
}
}
方法五:dp 究极优化
来自数学的优化。。。每次出现趋势变动的时候就会+1 那么不用max函数了就
具体的可以看注释,还看不懂就看这个。
class Solution {
public int wiggleMaxLength(int[] nums) {
int n=nums.length;
if(n<=1){
return n;
}
//dp算法
//优化了空间
//如果你又看到这个题了 一定要看看上个版本的详细注释
//注释写的很清楚了捏 这个是时间O(n) 空间O(1)的算法
//上个是时间O(n) 空间O(n)
//代码随想录的是时间O(n方) 空间O(n)
int up = 1;
int down = 1;
for(int i=1;i<n;i++){
if(nums[i]<nums[i-1]){
down = up+1;
}else if((nums[i]>nums[i-1])){
up = down+1;
}
}
//最长序列一定在两个数组最后
return Math.max(up,down);
}
}