每日算法系列【LeetCode 376】摆动序列

简介: 每日算法系列【LeetCode 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

题解

这题题面说的啰里啰唆的,其实就一句话:给你一个序列,找出最长的一个子序列,其中子序列相邻两个数的大小是波形的(也就是大小大小大等等这样的)。

暴力法

用 dfs 枚举所有可能的子序列,然后看最长的是多少,这种方法显然会超时。

动态规划

其实看到这道题,我第一个想到了最长上升子序列,这不就变了个形式嘛,于是动态规划解法直接就有了。

用  表示以  结尾的符合条件的最长子序列长度,其中 s 取 1 表示在  处子序列上升,取 0 表示下降。那么我们只需要遍历之前的所有 j ,如果  ,那么在 j 处必须是要下降的,更新:

如果  ,那么在 j 处必须是要上升的,更新:

然后取数组中最大值就是答案了,时间复杂度  。

动态规划+时间优化

换个定义,用  表示  之前的最长子序列,注意和上面的区别就是不一定以  结尾了。s 取 1 表示最后两个数是上升的,取 0 表示最后两个数是下降的。

这里分为几种情况:

  • :
  • 考虑  ,也就是最后两个数下降的,那肯定不能取  ,因为  比它更小、更优,所以直接更新为  。
  • 考虑  ,也就是最后两个数上升的,那如果不取  ,那更新为  ;如果取的话,我们就要保证 i-1 之前最后两个数是下降的,并且之前的最后一个数小于  。我们可以证明, i-1 之前的最后两个下降的数一定满足:第二个数  是小于  的,因为如果  ,那么 j 到 i 之间的数一定是单调下降的,否则存在更长的子序列,那么就和  矛盾了。综上,取的话  更新为  。
  • : 同样考虑最后两个数上升还是下降,分析和上面一样。

综上考虑,时间复杂度可以降为  ,空间复杂度是  。

动态规划+空间优化

在上面优化的基础上,我们还可以观察到,每一次  其实只会用到  ,所以我们只需要保存当前和前一时刻的状态就行了,空间复杂度可以降为  。

贪心法

其实这题还可以直接贪心做,考虑一段连续的上升序列,最优子序列一定是包括了首尾两个数的,因为首是最小的数,选了它才能给前一个数留出更大的上升空间,而尾是最大的数,选了它才能给下一个数留出更多的下降空间。

所以我们贪心的扫描一遍数组,遇到上升或者下降的转折点就选取这个数。而如果数组不升不降,也就是不变的话,就不用管它,因为这些相同的数里面只需要选取一个就行了。

时间复杂度是  ,空间复杂度是  。

代码

动态规划(c++)

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

动态规划+时间优化(c++)

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

动态规划+空间优化(c++)

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

贪心(c++)

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        int n = nums.size();
        if (n <= 1) return n;
        int res = 1, pre_ord = -1;
        for (int i = 1; i < n; ++i) {
            if (nums[i] == nums[i-1]) continue;
            int ord = nums[i] > nums[i-1];
            if (ord != pre_ord) res++;
            pre_ord = ord;
        }
        return res;
    }
};

后记

鼠年快乐,新年献给大家的第一道题,尽量写的详细一点。

官方题解没有严谨的证明,虽然方法也是这 5 种,但是没有说清楚,不能令人信服。

相关文章
|
1月前
|
算法
Leetcode 初级算法 --- 数组篇
Leetcode 初级算法 --- 数组篇
38 0
|
13天前
|
存储 算法 Java
leetcode算法题-有效的括号(简单)
【11月更文挑战第5天】本文介绍了 LeetCode 上“有效的括号”这道题的解法。题目要求判断一个只包含括号字符的字符串是否有效。有效字符串需满足左括号必须用相同类型的右括号闭合,并且左括号必须以正确的顺序闭合。解题思路是使用栈数据结构,遍历字符串时将左括号压入栈中,遇到右括号时检查栈顶元素是否匹配。最后根据栈是否为空来判断字符串中的括号是否有效。示例代码包括 Python 和 Java 版本。
|
1月前
|
算法
每日一道算法题(Leetcode 20)
每日一道算法题(Leetcode 20)
24 2
|
1月前
|
算法
动态规划算法学习四:最大上升子序列问题(LIS:Longest Increasing Subsequence)
这篇文章介绍了动态规划算法中解决最大上升子序列问题(LIS)的方法,包括问题的描述、动态规划的步骤、状态表示、递推方程、计算最优值以及优化方法,如非动态规划的二分法。
65 0
动态规划算法学习四:最大上升子序列问题(LIS:Longest Increasing Subsequence)
|
2月前
|
算法 数据可视化
基于SSA奇异谱分析算法的时间序列趋势线提取matlab仿真
奇异谱分析(SSA)是一种基于奇异值分解(SVD)和轨迹矩阵的非线性、非参数时间序列分析方法,适用于提取趋势、周期性和噪声成分。本项目使用MATLAB 2022a版本实现从强干扰序列中提取趋势线,并通过可视化展示了原时间序列与提取的趋势分量。代码实现了滑动窗口下的奇异值分解和分组重构,适用于非线性和非平稳时间序列分析。此方法在气候变化、金融市场和生物医学信号处理等领域有广泛应用。
125 19
|
2月前
|
算法 数据可视化 数据安全/隐私保护
基于LK光流提取算法的图像序列晃动程度计算matlab仿真
该算法基于Lucas-Kanade光流方法,用于计算图像序列的晃动程度。通过计算相邻帧间的光流场并定义晃动程度指标(如RMS),可量化图像晃动。此版本适用于Matlab 2022a,提供详细中文注释与操作视频。完整代码无水印。
|
3月前
|
机器学习/深度学习 数据采集 算法
【优秀python算法毕设】基于python时间序列模型分析气温变化趋势的设计与实现
本文介绍了一个基于Python的时间序列模型,用于分析和预测2021-2022年重庆地区的气温变化趋势,通过ARIMA和LSTM模型的应用,揭示了气温的季节性和趋势性变化,并提供了对未来气温变化的预测,有助于气象预报和相关决策制定。
【优秀python算法毕设】基于python时间序列模型分析气温变化趋势的设计与实现
|
3月前
|
算法 Java
LeetCode经典算法题:矩阵中省份数量经典题目+三角形最大周长java多种解法详解
LeetCode经典算法题:矩阵中省份数量经典题目+三角形最大周长java多种解法详解
52 6
|
3月前
|
人工智能 算法 Java
LeetCode经典算法题:井字游戏+优势洗牌+Dota2参议院java解法
LeetCode经典算法题:井字游戏+优势洗牌+Dota2参议院java解法
50 1
|
3月前
|
存储 算法 Java
LeetCode经典算法题:预测赢家+香槟塔java解法
LeetCode经典算法题:预测赢家+香槟塔java解法
60 1