一. Wiggle Subsequence
A sequence of numbers is called a wiggle sequence if the differences between successive numbers strictly alternate between positive and negative. The first difference (if one exists) may be either positive or negative. A sequence with fewer than two elements is trivially a wiggle sequence.
For example, [1,7,4,9,2,5] is a wiggle sequence because the differences (6,-3,5,-7,3) are alternately positive and negative. In contrast, [1,4,7,2,5] and [1,7,4,5,5] are not wiggle sequences, the first because its first two differences are positive and the second because its last difference is zero.
Given a sequence of integers, return the length of the longest subsequence that is a wiggle sequence. A subsequence is obtained by deleting some number of elements (eventually, also zero) from the original sequence, leaving the remaining elements in their original order.
Examples:
Input: [1,7,4,9,2,5]
Output: 6
The entire sequence is a wiggle sequence.Input: [1,17,5,10,13,15,10,5,16,8]
Output: 7
There are several subsequences that achieve this length. One is [1,17,10,13,10,16,8].Input: [1,2,3,4,5,6,7,8,9]
Output: 2
Follow up:
Can you do it in O(n) time?
Difficulty:Medium
TIME:TIMEOUT
解法一
这道题是求最长的锯齿状的子序列,当然看到最长,首先想到的就是动态规划。这道题动态规划的思想和求最长递增子序列十分相似。
如果采用
minus
数组表示以这个点作为锯齿子序列最低点结束的最长长度,采用
plus
数组表示以这个点作为锯齿子序列最高点结束的最长长度,而且令
X=<x1,x2,...,xi−1,xi>
为带求解序列的的任意一个前缀,其中
0<j<i
,这道题的最优子结构可以描述为:
- 如果 xi>xj ,那么 plus[i]=max(plus[i],minus[j]+1)
- 如果 xi<xj ,那么 minus[i]=max(minus[i],plus[j]+1)
代码如下:
int wiggleMaxLength(vector<int>& nums) {
if(nums.size() < 2)
return nums.size();
vector<int> minus(nums.size(),1);
vector<int> plus(nums.size(),1);
for(size_t i = 1; i < nums.size(); i++) {
for(int j = 0; j < i; j++) {
if(nums[i] > nums[j])
plus[i] = max(plus[i], minus[j] + 1);
if(nums[i] < nums[j])
minus[i] = max(minus[i], plus[j] + 1);
}
}
return max(plus[nums.size() - 1],minus[nums.size() - 1]);
}
代码的时间复杂度为 O(n2) 。
解法二
正如Follow up提示的一样,这道题存在着O(n)的解法。如果还有解法能比动态规划还要快的,那应该就是贪心算法了。
这里采用的贪心策略和求最长递增子序列类似,但还是有些差别的,起码复杂度比求最长递增子序列还要低。
这里的贪心策略简单来说就是保证锯齿的幅度最大化,比如给定序列
<1,4,9,5,2,3>
<script type="math/tex" id="MathJax-Element-10"><1,4,9,5,2,3></script>,如果采用贪心算法,那么求得的最长锯齿状的序列变化如下:
- <1,4> 初始化为两个数字
- <1,9> 发现9比4大,那么保证锯齿幅度最大,把4替换为9
- <1,9,5> 发现5比9小,加上5
- <1,9,2> 发现2比5小,保证锯齿幅度最大,把5替换为2
- <1,9,2,3> 发现3比2大,加上3,这就是这个序列的最长锯齿状序列
代码如下:
int wiggleMaxLength(vector<int>& nums) {
if(nums.size() < 2)
return nums.size();
vector<int> v{nums[0]};
for(int i = 1; i < nums.size(); i++) {
if(nums[i] == nums[i - 1]) //跳过相同数字
continue;
if(v.size() == 1) { //初始化为两个数字的数组
v.push_back(nums[i]);
continue;
}
int dis = v.back() - v[v.size() - 2];
if((nums[i] <= v.back() && dis < 0) || (nums[i] >= v.back() && dis > 0))
v[v.size() - 1] = nums[i];
else
v.push_back(nums[i]);
}
return v.size();
}
代码的时间复杂度为O(n)。
总结
很多题能用动态规划来求解,但不一定能用贪心策略来求解。不过一般情况下,贪心策略都要比动态规划的时间复杂度要低(动态规划在求最优解的过程中求了很多无关的解,而贪心只求最优解)。
知识点
动态规划
贪心策略