376. 摆动序列
Question
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 摆动序列 。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。
例如, [1, 7, 4, 9, 2, 5] 是一个 摆动序列 ,因为差值 (6, -3, 5, -7, 3) 是正负交替出现的。
相反,[1, 4, 7, 2, 5] 和 [1, 7, 4, 5, 5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。
子序列 可以通过从原始序列中删除一些(也可以不删除)元素来获得,剩下的元素保持其原始顺序。
给你一个整数数组 nums ,返回 nums 中作为 摆动序列 的 最长子序列的长度 。
Example 1:
输入:nums = [1,7,4,9,2,5] 输出:6 解释:整个序列均为摆动序列,各元素之间的差值为 (6, -3, 5, -7, 3) 。
Example 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) 。
Note:
1 <= nums.length <= 1000
0 <= nums[i] <= 1000
Approach 1: 贪心
一开始没看到可以删减元素,打算使用双循环,遍历每个元素作为开头的子序列,计算最长的摇摆子序列。但是跑例子2的时候处了问题。
后来发现可以删除元素,所以考虑第二层循环里面不同加break了,可以继续计数,同时考虑了几个特殊情况,例如
1 | class Solution: |
进一步补考虑,实际上由于删除机制,最长的子序列一定是从头开始计数的,所以可以去掉第一层循环,减小时间复杂度从O(n^2)到O(n)
1 | class Solution: |
复杂度分析
- 时间复杂度:
,N是序列长度。 - 空间复杂度:
。常数个变量。
Approach 2: 动态规划
每当我们选择一个元素作为摆动序列的一部分时,这个元素要么是上升的,要么是下降的,这取决于前一个元素的大小。那么列出状态表达式为:
表示以前 i 个元素中的某一个为结尾的最长的「上升摆动序列」的长度。 表示以前 i 个元素中的某一个为结尾的最长的「下降摆动序列」的长度。
下面以 up[i] 为例,说明其状态转移规则:
- 当
时,我们无法选出更长的「上升摆动序列」的方案。因为对于任何以 结尾的「上升摆动序列」,我们都可以将 替换为 ,使其成为以 结尾的「上升摆动序列」。 - 当
时,我们既可以从 进行转移,也可以从 进行转移。下面我们证明从 转移是必然合法的,即必然存在一个 对应的最长的「下降摆动序列」的末尾元素小于 。 - 我们记这个末尾元素在原序列中的下标为 j,假设从 j 往前的第一个「谷」为 nums[k],我们总可以让 j 移动到 k,使得这个最长的「下降摆动序列」的末尾元素为「谷」。
- 然后我们可以证明在这个末尾元素为「谷」的情况下,这个末尾元素必然是从 nums[i] 往前的第一个「谷」。证明非常简单,我们使用反证法,如果这个末尾元素不是从 nums[i] 往前的第一个「谷」,那么我们总可以在末尾元素和 nums[i] 之间取得一对「峰」与「谷」,接在这个「下降摆动序列」后,使其更长。
- 这样我们知道必然存在一个 down[i−1] 对应的最长的「下降摆动序列」的末尾元素为 nums[i] 往前的第一个「谷」。这个「谷」必然小于 nums[i]。证毕。
这样我们可以用同样的方法说明 down[i]
的状态转移规则,最终的状态转移方程为:
最终的答案即为 up[n−1] 和 down[n−1] 中的较大值,其中 n 是序列的长度。
1 | class Solution: |
复杂度分析
时间复杂度:O(n),其中 n 是序列的长度。我们只需要遍历该序列一次。
空间复杂度:O(n),其中 n 是序列的长度。我们需要开辟两个长度为 n 的数组。
Approach 3: 优化的动态规划
思路及解法
注意到方法一中,我们仅需要前一个状态来进行转移,所以我们维护两个变量即可。这样我们可以写出如下的代码:
1 | class Solution: |
注意到每有一个「峰」到「谷」的下降趋势,
1 | class Solution: |
复杂度分析
时间复杂度:O(n),其中 n 是序列的长度。我们只需要遍历该序列一次。
空间复杂度:O(1)。我们只需要常数空间来存放若干变量。