diff --git a/problems/0376.摆动序列.md b/problems/0376.摆动序列.md index 23348bd0..7da39526 100644 --- a/problems/0376.摆动序列.md +++ b/problems/0376.摆动序列.md @@ -33,8 +33,7 @@ 输入: [1,2,3,4,5,6,7,8,9] 输出: 2 - -## 思路 +## 思路1(贪心解法) 本题要求通过从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。 @@ -93,6 +92,69 @@ public: 时间复杂度O(n) 空间复杂度O(1) +## 思路2(动态规划) + +考虑用动态规划的思想来解决这个问题。 + +很容易可以发现,对于我们当前考虑的这个数,要么是作为山峰(即nums[i] > nums[i-1]),要么是作为山谷(即nums[i] < nums[i - 1])。 + +* 设dp状态`dp[i][0]`,表示考虑前i个数,第i个数作为山峰的摆动子序列的最长长度 +* 设dp状态`dp[i][1]`,表示考虑前i个数,第i个数作为山谷的摆动子序列的最长长度 + +则转移方程为: + +* `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]接到前面某个山峰后面,作为山谷。 + +初始状态: + +由于一个数可以接到前面的某个数后面,也可以以自身为子序列的起点,所以初始状态为:`dp[0][0] = dp[0][1] = 1`。 + +C++代码如下: + +```c++ +class Solution { +public: + int dp[1005][2]; + int wiggleMaxLength(vector& nums) { + memset(dp, 0, sizeof dp); + dp[0][0] = dp[0][1] = 1; + + for (int i = 1; i < nums.size(); ++i) + { + dp[i][0] = dp[i][1] = 1; + + for (int j = 0; j < i; ++j) + { + if (nums[j] > nums[i]) dp[i][1] = max(dp[i][1], dp[j][0] + 1); + } + + for (int j = 0; j < i; ++j) + { + if (nums[j] < nums[i]) dp[i][0] = max(dp[i][0], dp[j][1] + 1); + } + } + return max(dp[nums.size() - 1][0], dp[nums.size() - 1][1]); + } +}; +``` + +时间复杂度O(n^2) + +空间复杂度O(n) + +**进阶** + +可以用两棵线段树来维护区间的最大值 + +* 每次更新`dp[i][0]`,则在`tree1`的`nums[i]`位置值更新为`dp[i][0]` +* 每次更新`dp[i][1]`,则在`tree2`的`nums[i]`位置值更新为`dp[i][1]` +* 则dp转移方程中就没有必要j从0遍历到i-1,可以直接在线段树中查询指定区间的值即可。 + +时间复杂度O(nlogn) + +空间复杂度O(n) + ## 总结 **贪心的题目说简单有的时候就是常识,说难就难在都不知道该怎么用贪心**。