这是一道贪心及动态规划方面的题。做这道题的时候看官方题解的思路,我理解了好久才想通≡(▔﹏▔)≡在这里记录下我的推导思路,和官方有些区别;如果大家理解不了官方题解,可以看看这篇博文,希望有些帮助~
本文参考leetcode官方题解
原序列用 n u m s nums nums表示。题目中需要注意的是一个元素就可以构成摆动序列,两个不同的元素就构成一个长度为2的摆动序列。
我们使用动态规划的方法解题,但是在状态转移方程的推导过程中,需要根据贪心的思想!所以下面先介绍本题的贪心思想,再进行动态规划的思路讲解。
为了方便叙述,我们先定义一些简单的小概念,大家要记住哦:
我先直接写出解决本题需要的贪心思想:在原序列中,我们按照上升/下降趋势交替地选择[谷]和[峰](即…-[谷]-[峰]-[谷]-[峰]-…),这样可以使最终得到的摆动序列最长。原序列的首尾元素会依照上升/下降趋势出现在最长摆动序列中。
该思路的证明在官方题解中有讲,但是感觉不好理解。我们可以直观地想一下(可能并不严谨):
对于“原序列的首尾元素会依照上升/下降趋势出现在某一种摆动序列中”这句话,大家可能会有疑惑,下面来解释一下:
我们已经知道序列的首尾元素要么是[谷],要么是[峰](如果它们和相邻元素相等,那就忽略掉相邻元素,当作其不存在就好)。由于序列的首元素就是一个[谷]/[峰],所以按照贪心思想我们一定要它,之后按照上升/下降趋势交替地选择[谷]和[峰],一直遍历到尾元素;
如果尾元素是[谷],它前面一定有被选择的[峰],那么为了使摆动子序列最长,一定会把尾元素加进去,最终得到的最长摆动子序列结尾呈下降趋势;同理,若尾元素是[峰],最长的摆动子序列结尾一定呈上升趋势。
理解了贪心算法后,我们再来看如何应用动规解题。
首先,确定状态表达式。
(大家可以先回顾一下前面定义的小概念)
下面,推导状态转移方程。
我们遍历 n u m s nums nums(即原始序列),在遍历到 n u m s [ i ] nums[i] nums[i]时,考虑 u p [ i ] , d o w n [ i ] up[i], down[i] up[i],down[i]的更新。注意当前关注的子序列是 n u m s [ : i + 1 ] nums[:i+1] nums[:i+1]。
这里根据 n u m s [ i ] nums[i] nums[i]的值可以分成三种情况:
(推导预警)
综合前面两种小情况,可得, u p [ i ] = m a x ( d o w n [ i − 1 ] + 1 , u p [ i − 1 ] ) up[i]=max(down[i-1]+1, up[i-1]) up[i]=max(down[i−1]+1,up[i−1]) (二者取最大), d o w n [ i ] = d o w n [ i − 1 ] down[i]=down[i-1] down[i]=down[i−1]
综合前面两种小情况,可得, u p [ i ] = u p [ i − 1 ] ) up[i]=up[i-1]) up[i]=up[i−1]) , d o w n [ i ] = m a x ( d o w n [ i − 1 ] , u p [ i − 1 ] + 1 ) down[i]=max(down[i-1], up[i-1]+1) down[i]=max(down[i−1],up[i−1]+1)(二者取最大)
最后在遍历完 n u m s nums nums后,我们返回 u p [ n − 1 ] , d o w n [ n − 1 ] up[n-1], down[n-1] up[n−1],down[n−1]( n = l e n ( n u m s ) n=len(nums) n=len(nums))中的最大值作为结果。
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])
由于在状态转移的过程中,当前状态只和上一个状态有关,因此我们可以进行以下简化,降低空间复杂度:
class Solution:
def wiggleMaxLength(self, nums: List[int]) -> int:
n = len(nums)
if n < 2:
return n
up = down = 1
for i in range(1, n):
if nums[i] > nums[i - 1]:
up = max(up, down + 1)
elif nums[i] < nums[i - 1]:
down = max(up + 1, down)
return max(up, down)
讲道理,这题好绕啊,虽然写出来的代码看上去很简单,但是想明白就需要花时间了…动态规划的状态转移方程推导着实是最难的部分,需要耐心。
每个人可能都有自己的理解方式,有时候官方题解里面的不一定适合自己理解,最好还是用自己的方式推一遍。
这个题解我写的也好不容易啊〒▽〒 希望有帮助吧~