376. 摆动序列
这个题既可以用贪心做,也可以用动态规划做。
贪心算法
要使用贪心算法最关键的条件是局部最优能够推出整体最优,即小局部的最优可以推出大局部的最优,局部慢慢变大就成了整体,最后推出整体最优,这里很明显整体和局部的构造是相同的,所以可以用贪心算法,那怎么找局部最优呢,这道题就是给一个局部,然后把其中不符合条件的直接删除即可,我们可以整体序列看作这样的一个图:
图片来源:代码随想录
局部最优就是删除那些连续向上或向下的点,所以我们只需要判断当前点是否为连续点还是波峰或波谷,若是后两者,则摆动序列最大长度加1,注意最后一个节点我们没有判断,而最后一个节点总能成为波峰或波谷,所以一开始的长度就为1。代码如下:
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
if (nums.size() <= 1)
return nums.size();
int curdiff;
int prediff = 0;
int result = 1;
for (int i = 0; i < nums.size() - 1; i++) {
curdiff = nums[i + 1] - nums[i];
if (prediff >= 0 && curdiff < 0 || prediff <= 0 && curdiff > 0) {
result++;
prediff = curdiff;
}
}
return result;
}
};
动态规划:
动态规划是使用前面的信息求取后面的数据,求后面的数据时往往要比较两种情况的好坏。
这里的动态规划时间复杂度会高一些而且不容易想到。完成一个动态规划相当于填一个表,即dp数组。
首先搞懂dp[i] [j]的含义,dp[i] [0]表示第i个数做波峰时前i个数摆动序列最大长度,dp[i] [1]表示第j个数做波谷时前i个数摆动序列最大长度。
每一个数即可与前面的数组合,又可自己作为一个序列的开端(此时长度为1),所以要比较各种情况的好坏。
即:
dp[i][0] = max(dp[i][0], dp[j][1] + 1)
,其中0 < j < i
且nums[j] < nums[i]
,表示将nums[i]接到前面某个山谷后面,作为山峰。dp[i][1] = max(dp[i][1], dp[j][0] + 1)
,其中0 < j < i
且nums[j] > nums[i]
,表示将nums[i]接到前面某个山峰后面,作为山谷。
代码如下:
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
int dp[1005][2];
dp[0][0] = 1;
dp[0][1] = 1;
for (int i = 1; i < nums.size(); i++) {
dp[i][0] = 1;
dp[i][1] = 1;
for (int j = 0; j < i; j++) {
if (nums[i] > nums[j]) {
dp[i][0] = max(dp[i][0], dp[j][1] + 1);
}
}
for (int j = 0; j < i; j++) {
if (nums[i] < nums[j]) {
dp[i][1] = max(dp[i][1], dp[j][0] + 1);
}
}
}
return max(dp[nums.size() - 1][0], dp[nums.size() - 1][1]);
}
};
总结
贪心算法使用的条件是局部最优能推出整体最优,即局部与整体构造相同。使用的方法是从头开始,扩大局部直到整体,每一步都要保证最优。贪心算法最关键的点在于扩大局部时保持最优。
动态规划必须满足最优化原理和无后效性才适用动态规划。
最优化原理(最优子结构性质):一个最优化策略的子策略总是最优的。所以不管过去的过程如何,只从当前的状态和系统的最优化要求出发,作出下一步的最优决策。
无后效性:对于某个给定的阶段状态,它以前各阶段的状态无法直接影响它未来的决策。换句话说,每个状态都是过去历史的一个完整总结。
动态规划最重要的有三点:dp[i] [j]的含义;转移方程;dp初始化
阶段状态,它以前各阶段的状态无法直接影响它未来的决策。换句话说,每个状态都是过去历史的一个完整总结。
动态规划最重要的有三点:dp[i] [j]的含义;转移方程;dp初始化。