376.摆动序列
greedy, dp, https://leetcode.cn/problems/wiggle-subsequence/
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 摆动序列 。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。
- 例如,
[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 <= 10000 <= nums[i] <= 1000
进阶:你能否用 O(n) 时间复杂度完成此题?
某个序列被称为「上升摆动序列」,当且仅当该序列是摆动序列,且最后一个元素呈上升趋势。如序列 [1,3,2,4] 即为「上升摆动序列」。
某个序列被称为「下降摆动序列」,当且仅当该序列是摆动序列,且最后一个元素呈下降趋势。如序列 [4,2,3,1] 即为「下降摆动序列」。
up[i] 表示以前 i 个元素中的某一个为结尾的最长的「上升摆动序列」的长度。
down[i] 表示以前 i 个元素中的某一个为结尾的最长的「下降摆动序列」的长度。
class Solution:
def wiggleMaxLength(self, nums: List[int]) -> int:
n = len(nums)
if n < 2:
return n
up = [1] + [0] * (n - 1)
down = [1] + [0] * (n - 1)
for i in range(1, n):
if nums[i] > nums[i - 1]:
up[i] = max(up[i - 1], down[i - 1] + 1)
down[i] = down[i - 1]
elif nums[i] < nums[i - 1]:
up[i] = up[i - 1]
down[i] = max(up[i - 1] + 1, down[i - 1])
else:
up[i] = up[i - 1]
down[i] = down[i - 1]
return max(up[n - 1], down[n - 1])利用动态规划分别记录到每个位置的最长摆动序列长度(up 和 down),并且在每一步保持这两个数组的更新。由于在摆动序列中,当前状态只能是“上升”或“下降”,因此这种方法可以保证 up 和 down 之间的差值不会超过 1。
class Solution:
def wiggleMaxLength(self, nums: List[int]) -> int:
n = len(nums)
if n < 2:
return n
up = [1] + [0] * (n - 1)
down = [1] + [0] * (n - 1)
for i in range(1, n):
if nums[i] > nums[i - 1]:
up[i] = down[i - 1] + 1
down[i] = down[i - 1]
elif nums[i] < nums[i - 1]:
up[i] = up[i - 1]
down[i] = up[i - 1] + 1
else:
up[i] = up[i - 1]
down[i] = down[i - 1]
return max(up[n - 1], down[n - 1])Plan
- Initialize two variables
upanddownto 1. These will keep track of the length of the longest wiggle subsequence ending with an upward or downward wiggle, respectively. - Iterate through the
numsarray starting from the second element. - For each element, compare it with the previous element:
- If the current element is greater than the previous element, update
uptodown + 1. - If the current element is less than the previous element, update
downtoup + 1.
- If the current element is greater than the previous element, update
- The result will be the maximum value between
upanddown.
这个算法实际上结合了贪心(Greedy)和动态规划(Dynamic Programming, DP)的思想。让我们详细分析一下:
贪心(Greedy)思想
贪心算法通常在每一步选择局部最优解,希望最终得到全局最优解。在这个问题中,每当我们遇到一个上升或下降的摆动时,我们都会立即更新 up 或 down,这看起来像是贪心的选择。
动态规划(DP)思想
动态规划通过将问题分解成子问题,并保存子问题的解来避免重复计算。在这个问题中,up 和 down 分别表示以当前元素结尾的最长上升摆动序列和最长下降摆动序列的长度,这是典型的动态规划状态定义。
class Solution:
def wiggleMaxLength(self, nums: List[int]) -> int:
if not nums:
return 0
up = down = 1
for i in range(1, len(nums)):
if nums[i] > nums[i - 1]:
up = down + 1
elif nums[i] < nums[i - 1]:
down = up + 1
return max(up, down)这是一个贪心问题,考虑局部最优的情形,我们需要将两个峰值之间的数全部删去,来保证子序列中每个元素都是摆动的。 但考虑到有非严格递增的情形,如1,2,2,2,1,摆动长度为3。我们可以设置一个trend来表示前一个摆动的趋势,如初始/非严格单调为0,递增为1,递减为-1。 那么只需要nums[i] > nums[i-1] 且trend ≤ 0 就能表示出现了一个递增的摆动,递减的摆动同理。最后统计摆动个数即可。
# 徐梓文 24医学预科办
from typing import List
class Solution:
def wiggleMaxLength(self, nums: List[int]) -> int:
ans,trend=1,0
for i in range(1,len(nums)):
if nums[i]>nums[i-1] and trend<=0:
ans+=1
trend=1
if nums[i]<nums[i-1] and trend>=0:
ans+=1
trend=-1
return ans