docs: 摆动序列添加视频讲解链接

This commit is contained in:
liangzhensheng
2023-03-17 11:11:35 +08:00
parent c3588f445f
commit 5678f566a8

View File

@ -4,7 +4,6 @@
</a>
<p align="center"><strong><a href="https://mp.weixin.qq.com/s/tqCxrMEU-ajQumL1i8im9A">参与本项目</a>,贡献其他语言版本的代码,拥抱开源,让更多学习算法的小伙伴们收益!</strong></p>
> 本周讲解了[贪心理论基础](https://programmercarl.com/贪心算法理论基础.html),以及第一道贪心的题目:[贪心算法:分发饼干](https://programmercarl.com/0455.分发饼干.html),可能会给大家一种贪心算法比较简单的错觉,好了,接下来几天的题目难度要上来了,哈哈。
# 376. 摆动序列
@ -13,25 +12,32 @@
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第一个差(如果存在的话)可能是正数或负数。少于两个元素的序列也是摆动序列。
例如, [1,7,4,9,2,5] 是一个摆动序列,因为差值 (6,-3,5,-7,3) 是正负交替出现的。相反, [1,4,7,2,5]  [1,7,4,5,5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。
例如, [1,7,4,9,2,5] 是一个摆动序列,因为差值 (6,-3,5,-7,3)  是正负交替出现的。相反, [1,4,7,2,5]  [1,7,4,5,5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。
给定一个整数序列,返回作为摆动序列的最长子序列的长度。 通过从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。
示例 1:
* 输入: [1,7,4,9,2,5]
*: 6
* 解释: 整个序列均为摆动序列。
-: [1,7,4,9,2,5]
- 输出: 6
- 解释: 整个序列均为摆动序列。
示例 2:
* 输入: [1,17,5,10,13,15,10,5,16,8]
*: 7
* 解释: 这个序列包含几个长度为 7 摆动序列,其中一个可为[1,17,10,13,10,16,8]。
-: [1,17,5,10,13,15,10,5,16,8]
- 输出: 7
- 解释: 这个序列包含几个长度为 7 摆动序列,其中一个可为[1,17,10,13,10,16,8]。
示例 3:
* 输入: [1,2,3,4,5,6,7,8,9]
* 输出: 2
## 思路1贪心解法
- 输入: [1,2,3,4,5,6,7,8,9]
- 输出: 2
# 视频讲解
**《代码随想录》算法视频公开课:[贪心算法,寻找摆动有细节!| LeetCode376.摆动序列](https://www.bilibili.com/video/BV17M411b7NS),相信结合视频在看本篇题解,更有助于大家对本题的理解**。
## 思路 1贪心解法
本题要求通过从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。
@ -55,7 +61,7 @@
**这就是贪心所贪的地方,让峰值尽可能的保持峰值,然后删除单一坡度上的节点**
在计算是否有峰值的时候大家知道遍历的下标i 计算prediffnums[i] - nums[i-1] 和 curdiffnums[i+1] - nums[i]),如果`prediff < 0 && curdiff > 0` 或者 `prediff > 0 && curdiff < 0` 此时就有波动就需要统计。
在计算是否有峰值的时候,大家知道遍历的下标 i ,计算 prediffnums[i] - nums[i-1] 和 curdiffnums[i+1] - nums[i]),如果`prediff < 0 && curdiff > 0` 或者 `prediff > 0 && curdiff < 0` 此时就有波动就需要统计。
这是我们思考本题的一个大题思路,但本题要考虑三种情况:
@ -69,31 +75,29 @@
![](https://code-thinking-1253855093.file.myqcloud.com/pics/20230106170449.png)
它的摇摆序列长度是多少呢? **其实是长度是3**,也就是我们在删除的时候 要不删除左面的三个2要不就删除右边的三个2。
它的摇摆序列长度是多少呢? **其实是长度是 3**,也就是我们在删除的时候 要不删除左面的三个 2要不就删除右边的三个 2。
如图可以统一规则删除左边的三个2
如图,可以统一规则,删除左边的三个 2
![](https://code-thinking-1253855093.file.myqcloud.com/pics/20230106172613.png)
在图中,当i指向第一个2的时候,`prediff > 0 && curdiff = 0` ,当 i 指向最后一个2的时候 `prediff = 0 && curdiff < 0`
在图中,当 i 指向第一个 2 的时候,`prediff > 0 && curdiff = 0` ,当 i 指向最后一个 2 的时候 `prediff = 0 && curdiff < 0`
如果我们采用,删左面三个2的规则,那么 当 `prediff = 0 && curdiff < 0` 也要记录一个峰值,因为他是把之前相同的元素都删掉留下的峰值。
如果我们采用,删左面三个 2 的规则,那么 当 `prediff = 0 && curdiff < 0` 也要记录一个峰值,因为他是把之前相同的元素都删掉留下的峰值。
所以我们记录峰值的条件应该是: `(preDiff <= 0 && curDiff > 0) || (preDiff >= 0 && curDiff < 0)`,为什么这里允许 prediff == 0 ,就是为了 上面我说的这种情况。
### 情况二:数组首尾两端
所以本题统计峰值的时候,数组最左面和最右面如果统计呢?
题目中说了如果只有两个不同的元素那摆动序列也是2。
题目中说了,如果只有两个不同的元素,那摆动序列也是 2。
例如序列[2,5],如果靠统计差值来计算峰值个数就需要考虑数组最左面和最右面的特殊情况。
因为我们在计算 prediffnums[i] - nums[i-1] 和 curdiffnums[i+1] - nums[i])的时候,至少需要三个数字才能计算,而数组只有两个数字。
这里我们可以写死,就是 如果只有两个元素且元素不同那么结果为2。
这里我们可以写死,就是 如果只有两个元素,且元素不同,那么结果为 2。
不写死的话,如果和我们的判断规则结合在一起呢?
@ -101,11 +105,11 @@
之前我们在 讨论 情况一:相同数字连续 的时候, prediff = 0 curdiff < 0 或者 >0 也记为波谷。
那么为了规则统一,针对序列[2,5],可以假设为[2,2,5]这样它就有坡度了即preDiff = 0如图
那么为了规则统一,针对序列[2,5],可以假设为[2,2,5],这样它就有坡度了即 preDiff = 0如图
![376.摆动序列1](https://code-thinking-1253855093.file.myqcloud.com/pics/20201124174357612.png)
针对以上情形result初始为1默认最右面有一个峰值此时curDiff > 0 && preDiff <= 0那么result++计算了左面的峰值最后得到的result就是2峰值个数为2即摆动序列长度为2
针对以上情形result 初始为 1默认最右面有一个峰值此时 curDiff > 0 && preDiff <= 0那么 result++(计算了左面的峰值),最后得到的 result 就是 2峰值个数为 2 即摆动序列长度为 2
经过以上分析后,我们可以写出如下代码:
@ -130,8 +134,9 @@ public:
}
};
```
* 时间复杂度O(n)
* 空间复杂度O(1)
-间复杂度O(n)
- 空间复杂度O(1)
此时大家是不是发现 以上代码提交也不能通过本题?
@ -143,13 +148,13 @@ public:
![](https://code-thinking-1253855093.file.myqcloud.com/pics/20230108171505.png)
图中我们可以看出版本一的代码在三个地方记录峰值但其实结果因为是2因为 单调中的平坡 不能算峰值(即摆动)。
图中,我们可以看出,版本一的代码在三个地方记录峰值,但其实结果因为是 2因为 单调中的平坡 不能算峰值(即摆动)。
之所以版本一会出问题,是因为我们实时更新了 prediff。
那么我们应该什么时候更新prediff呢
那么我们应该什么时候更新 prediff 呢?
我们只需要在 这个坡度 摆动变化的时候更新prediff就行这样prediff在 单调区间有平坡的时候 就不会发生变化,造成我们的误判。
我们只需要在 这个坡度 摆动变化的时候,更新 prediff 就行,这样 prediff 在 单调区间有平坡的时候 就不会发生变化,造成我们的误判。
所以本题的最终代码为:
@ -182,19 +187,19 @@ public:
![](https://code-thinking-1253855093.file.myqcloud.com/pics/20230108174452.png)
## 思路2动态规划
## 思路 2动态规划
考虑用动态规划的思想来解决这个问题。
很容易可以发现对于我们当前考虑的这个数要么是作为山峰即nums[i] > nums[i-1]要么是作为山谷即nums[i] < nums[i - 1])。
很容易可以发现,对于我们当前考虑的这个数,要么是作为山峰(即 nums[i] > nums[i-1]),要么是作为山谷(即 nums[i] < nums[i - 1])。
* 设dp状态`dp[i][0]`表示考虑前i个数第i个数作为山峰的摆动子序列的最长长度
* 设dp状态`dp[i][1]`表示考虑前i个数第i个数作为山谷的摆动子序列的最长长度
- 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[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]接到前面某个山峰后面作为山谷
初始状态
@ -223,28 +228,25 @@ public:
};
```
* 时间复杂度O(n^2)
* 空间复杂度O(n)
- 时间复杂度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可以直接在线段树中查询指定区间的值即可
- 每次更新`dp[i][0]`则在`tree1``nums[i]`位置值更新为`dp[i][0]`
- 每次更新`dp[i][1]`则在`tree2``nums[i]`位置值更新为`dp[i][1]`
- dp 转移方程中就没有必要 j 0 遍历到 i-1可以直接在线段树中查询指定区间的值即可
时间复杂度O(nlog n)
空间复杂度O(n)
## 其他语言版本
### Java
```Java
class Solution {
public int wiggleMaxLength(int[] nums) {
@ -270,6 +272,7 @@ class Solution {
}
}
```
```java
// DP
class Solution {
@ -360,6 +363,7 @@ class Solution:
### Go
**贪心**
```go
func wiggleMaxLength(nums []int) int {
n := len(nums)
@ -383,6 +387,7 @@ func wiggleMaxLength(nums []int) int {
```
**动态规划**
```go
func wiggleMaxLength(nums []int) int {
n := len(nums)
@ -420,7 +425,9 @@ func max(a, b int) int {
```
### Javascript
**贪心**
```Javascript
var wiggleMaxLength = function(nums) {
if(nums.length <= 1) return nums.length
@ -437,7 +444,9 @@ var wiggleMaxLength = function(nums) {
return result
};
```
**动态规划**
```Javascript
var wiggleMaxLength = function(nums) {
if (nums.length === 1) return 1;
@ -458,7 +467,9 @@ var wiggleMaxLength = function(nums) {
```
### Rust
**贪心**
```Rust
impl Solution {
pub fn wiggle_max_length(nums: Vec<i32>) -> i32 {
@ -504,6 +515,7 @@ impl Solution {
```
### C
**贪心**
```c
@ -568,8 +580,6 @@ int wiggleMaxLength(int* nums, int numsSize){
}
```
### TypeScript
**贪心**
@ -583,16 +593,13 @@ function wiggleMaxLength(nums: number[]): number {
let count: number = 1;
for (let i = 1; i < length; i++) {
curDiff = nums[i] - nums[i - 1];
if (
(preDiff <= 0 && curDiff > 0) ||
(preDiff >= 0 && curDiff < 0)
) {
if ((preDiff <= 0 && curDiff > 0) || (preDiff >= 0 && curDiff < 0)) {
preDiff = curDiff;
count++;
}
}
return count;
};
}
```
**动态规划**
@ -601,7 +608,7 @@ function wiggleMaxLength(nums: number[]): number {
function wiggleMaxLength(nums: number[]): number {
const length: number = nums.length;
if (length <= 1) return length;
const dp: number[][] = new Array(length).fill(0).map(_ => []);
const dp: number[][] = new Array(length).fill(0).map((_) => []);
dp[0][0] = 1; // 第一个数作为波峰
dp[0][1] = 1; // 第一个数作为波谷
for (let i = 1; i < length; i++) {
@ -615,7 +622,7 @@ function wiggleMaxLength(nums: number[]): number {
}
}
return Math.max(dp[length - 1][0], dp[length - 1][1]);
};
}
```
### Scala