(LeetCode 376)摆动序列 [动态规划 O(n) 和 O(n^2) 解答 详细思路 ]

376. 摆动序列

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

例如, [1,7,4,9,2,5] 是一个摆动序列,因为差值 (6,-3,5,-7,3) 是正负交替出现的。相反, [1,4,7,2,5] 和 [1,7,4,5,5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。

给定一个整数序列,返回作为摆动序列的最长子序列的长度。 通过从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。

示例 1:

输入: [1,7,4,9,2,5]
输出: 6
解释: 整个序列均为摆动序列。
示例 2:

输入: [1,17,5,10,13,15,10,5,16,8]
输出: 7
解释: 这个序列包含几个长度为 7 摆动序列,其中一个可为[1,17,10,13,10,16,8]。
示例 3:

输入: [1,2,3,4,5,6,7,8,9]
输出: 2
进阶:
你能否用 O(n) 时间复杂度完成此题?

分析:
最长的摆动序列,可以通过 "在前面一些已知的摆动序列后面,添加相应的‘上升沿’或‘下降沿’得到。因此,很容易想到使用动态规划来求解。

状态表示:
对于每一个数nums[i],以它为结尾的摆动序列有两种情况
(1) 最后以‘下降沿’的方式到达nums[i]
(2) 最后以‘上升沿’的方式达到nums[i]
我们使用 dp[i][0] 表示: 最后以‘下降沿’的方式到达nums[i]的摆动序列的最大长度
我们使用 dp[i][1] 表示: 最后以‘上升沿’的方式到达nums[i]的摆动序列的最大长度

初始状态: dp[i][0] = dp[i][1] = 1;

状态转移:
由上述可知:dp[i][1] ( 或dp[i][0] ) 的值可以由前面所有比他小(或大)的数更新

dp[i][0] = max( dp[j][1] + 1 ) ( 0 <= j < i && a[i] < a[j] )
dp[i][1] = max( dp[j][0] + 1 ) ( 0 <= j < i && a[i] > a[j] )

AC代码: O(n^2)

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        int n = nums.size();
        if(n == 0) return 0;
        int ans = 1;
        int dp[n][2];
        memset(dp,0,sizeof(dp));
        dp[0][0] = 1; dp[0][1] = 1;
        for(int i=1;i<n;i++)
        {
            dp[i][0] = 1; dp[i][1] = 1;
            for(int j=i-1;j>=0;j--)
            {
                if(nums[i] > nums[j])
                {
                    dp[i][1] = max(dp[i][1], dp[j][0]+1);
                }
                else if(nums[i] < nums[j])
                {
                    dp[i][0] = max(dp[i][0], dp[j][1]+1);
                }
                else if(nums[i] == nums[j])
                {
                    dp[i][0] = max(dp[i][0],dp[j][0]);
                    dp[i][1] = max(dp[i][1],dp[j][1]);
                }
            }
            ans = max(ans, dp[i][0]);
            ans = max(ans, dp[i][1]);
        }
        return ans;
    }
};

改进优化:O(n)
在O(n^2)算法中,状态的转移和前面所有的状态有关。
而要想将时间复杂度降到O(n),我们只能将状态的转移关系固定,即:当前状态的更新只与它前面某一个状态有关。通常是和 i-1 的状态有关。

因此,我们的状态描述也发生了相应的变化:
状态描述:

dp[i][0] : 在前 i 个数中,可以得到的最长摆动序列的长度,并且最长摆动序列以‘下降沿’结尾。
dp[i][1] : 在前 i 个数中,可以得到的最长摆动序列的长度,并且最长摆动序列以‘上升沿’结尾。

初始状态: dp[i][0] = dp[i][1] = 1;

状态转移:

先给出结论:
if nums[i] > nums[i-1] : dp[i][1] = dp[i-1][0] + 1 ; dp[i][0] = dp[i-1][0];
if nums[i] < nums[i-1] : dp[i][0] = dp[i-1][1] + 1 ; dp[i][1] = dp[i-1][1];
if nums[i] = nums[i-1] : dp[i][1] = dp[i-1][1] ; dp[i][0] = dp[i-1][0];

我们仅仅通过比较nums[i] 与 nums[i-1] 的大小,就可以确定dp[i][1]和dp[i]0]的变化。
解释:
if nums[i] > nums[i-1] : dp[i][1] = dp[i-1][0] + 1 ; 换句话说就是,当nums[i] > nums[i-1]时,在当前以下降沿结尾的最长摆动序列后,一定可以添加一个‘上升沿’。
即: 假设该最长序列的结尾数为nums[k],倒数第2个数为nums[t] (t < k < i)

分类讨论:
1:若 nums[k] < nums[i] ,则可以直接添加一个‘上升沿’ nums[i-1] -> nums[i]。
2:若 nums[k] >= nums[i] ,由nums[i] > nums[i-1] 可知,nums[k] > nums[i-1],
则可将该最长序列的最后一个‘下降沿’,从 nums[t] ->nums[k] 换成 nums[t] -> nums[i-1] , 长度不变,就可以在后面继续添加‘上升沿’ nums[i-1] -> nums[i] 了。

if nums[i] > nums[i-1] : dp[i][0] = dp[i-1][0], 相当于直接不考虑nums[i] (‘删去’)

其他情况同理可得,不在重复讲解。

AC代码:

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        int n = nums.size();
        if(n == 0) return 0;
        int ans = 1;
        int dp[n][2];
        memset(dp,0,sizeof(dp));
        dp[0][0] = 1; dp[0][1] = 1;
        for(int i=1;i<n;i++)
        {
            dp[i][0] = 1; dp[i][1] = 1;
            if(nums[i] > nums[i-1])
            {
                dp[i][1] = dp[i-1][0]+1;
                dp[i][0] = dp[i-1][0];
            }
            else if(nums[i] < nums[i-1])
            {
                dp[i][0] = dp[i-1][1]+1;
                dp[i][1] = dp[i-1][1];
            }
            else if(nums[i] == nums[i-1])
            {
                dp[i][0] = dp[i-1][0];
                dp[i][1] = dp[i-1][1];
            }
            ans = max(ans, dp[i][0]);
            ans = max(ans, dp[i][1]);
        }
        return ans;
    }
};
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值