目录
吐槽
怎么说呢,疯狂的打了十天游戏现在属于是终于玩累了,于是我关掉了游戏打开了csdn,非常迷茫,不知道在写点什么内容了。这是属于一个博主的无奈更是一位学生的悲哀。于是我想到了这个世界上的一个BUG一般的存在就是学一下算法吧。就像你在高中时期的时候,你学学数学永远不会有错 。ok那就进入今天的算法环节,我的计划是一天两更,每天更新两道算法题然后研究一下机器学习这些大模型,这种文章可能就要几天一更了。
题目链接:376. 摆动序列 - 力扣(LeetCode)
思路
说实话刚开始看到这道题的时候我第一反应是直接暴力,先遍历出来这个序列的所有的子序列然后判断这个序列是否满足摆动序列对吧。注意了这里只有一个序列和只有两个序列都可以被称作摆动序列。
约定
我看了一下官方的题解来说,真的是很有实力的说法,所以我也参考一下官方的解题思路。
下面是一些约定。
一个序列可以被标记为“上升摆动序列”,只有在这个序列是一个摆动序列,并且它的最后一个元素呈现出上升的趋势时。例如,序列 [1,3,2,4] 就符合“上升摆动序列”的定义。
相对地,当一个序列本身是摆动的,并且其终结元素展现出下降趋势时,这样的序列则被定义为“下降摆动序列”。举个例子,序列 [4,2,3,1] 就属于“下降摆动序列”。
值得一提的是,对于只有一个元素的序列,既可以视作“上升摆动序列”,也可以视作“下降摆动序列”。 在这样的序列中,如果某个元素的左右两侧元素都比它小,那么这个元素我们称之为“峰”。比如在序列 [1,3,2,4] 中,数字3就构成了一个“峰”。
同样地,如果一个元素的两侧都比它大,这样的元素就被称作“谷”。以序列 [1,3,2,4] 为例,数字2即为一个“谷”。
特殊情况下,如果序列两端的元素只有一侧相邻元素比它大或小,我们也将其认为是“峰”或“谷”。例如在序列 [1,3,2,4] 中,1就是一个“谷”,而4则是一个“峰”。
如果序列中的任意两个相邻元素都不相同,即总是一个大于另一个,那么序列里既不是“峰”也不是“谷”的元素,我们将其称作“过渡元素”。如序列 [1,2,3,4] 中,数字2和3就是这样的“过渡元素”。
动态规划
思路
每当我们选择一个元素作为摆动序列的一部分时,这个元素要么是上升的,要么是下降的,这取决于前一个元素的大小。我们可以定义两个状态:
up[i]
表示以前 i 个元素中的某一个,作为结尾的最长的「上升摆动序列」的长度。down[i]
表示以前 i 个元素中的某一个,作为结尾的最长的「下降摆动序列」的长度。
以下是 up[i]
的规则:
- 当
nums[i] ≤ nums[i−1]
时,我们无法选出更长的「上升摆动序列」,因为可以将nums[i]
替换为nums[i−1]
,使其成为以nums[i−1]
结尾的「上升摆动序列」。也就是nums[i] = nums[i - 1]。
- 当
nums[i] > nums[i−1]
时,我们既可以从up[i−1]
进行转移,也可以从down[i−1]
进行转移。实际上,从down[i−1]
转移是必然合法的,即存在一个down[i−1]
对应的最长的「下降摆动序列」的末尾元素小于nums[i]
。
为什么这里从我们从down[i−1]
转移是必然合法的。记这个末尾元素在原序列中的下标为 j,假设从 j 往前的第一个「谷」为 nums[k]
,我们可以让 j 移动到 k,使得这个最长的「下降摆动序列」的末尾元素为「谷」。这样,我们知道必然存在一个 down[i−1]
对应的最长的「下降摆动序列」的末尾元素为 nums[i]
往前的第一个「谷」,这个「谷」必然小于 nums[i]
。
同样的方法可以用来说明 down[i]
的递推规则,也就是up[i−1]
转移是必然合法的。最终的递推公式为:
- 当
nums[i] ≤ nums[i−1]
时,up[i] = up[i−1]
- 当
nums[i] > nums[i−1]
时,up[i] = max(up[i−1], down[i−1] + 1)
- 当
nums[i] ≥ nums[i−1]
时,down[i] = down[i−1]
- 当
nums[i] < nums[i−1]
时,down[i] = max(up[i−1] + 1, down[i−1])
最终答案即为 up[n−1]
和 down[n−1]
中的较大值,其中 n 是序列的长度。
代码实现Java
class Solution {
public int wiggleMaxLength(int[] nums) {
// 获取到序列的长度
int length = nums.length;
// 如果长度为1或者长度为2直接返回长度
if (length < 2){
return length;
}
// 初始化up[]数组和down[]数组
int[] up = new int[length];
int[] down = new int[length];
// 初始化第一个元素的最长为1
up[0] = 1;
down[0] = 1;
// 进入主体判断逻辑
// 注意这里i = 0已经初始化好了所以从1开始
for (int i = 1; i < length; i++) {
// 然后就根据我们的递推公式写出如下的代码
// 由于递推公式中是四种情况,并且每种情况之间是有交叉的
// 所以还需要整理成如下的三种情况
if (nums[i] < nums[i - 1]){
// 小于的时候
up[i] = up[i - 1];
down[i] = Math.max(up[i - 1] + 1, down[i - 1]);
}else if (nums[i] > nums[i - 1]){
// 大于的时候
up[i] = Math.max(up[i - 1], down[i - 1] + 1);
down[i] = down[i - 1];
}else {
// 等于的时候
up[i] = up[i - 1];
down[i] = down[i - 1];
}
}
return Math.max(up[length - 1],down[length - 1] );
}
}
代码实现C++
class Solution {
public:
int wiggleMaxLength(vector<int>& nums) {
// 获取到序列的长度
int length = nums.size();
// 如果长度为1或者长度为2直接返回长度
if (length < 2) {
return length;
}
// 初始化up[]数组和down[]数组
vector<int> up(length, 1);
vector<int> down(length, 1);
// 进入主体判断逻辑
// 注意这里i = 0已经初始化好了所以从1开始
for (int i = 1; i < length; i++) {
// 然后就根据我们的递推公式写出如下的代码
// 由于递推公式中是四种情况,并且每种情况之间是有交叉的
// 所以还需要整理成如下的三种情况
if (nums[i] < nums[i - 1]) {
// 小于的时候
up[i] = up[i - 1];
down[i] = max(up[i - 1] + 1, down[i - 1]);
} else if (nums[i] > nums[i - 1]) {
// 大于的时候
up[i] = max(up[i - 1], down[i - 1] + 1);
down[i] = down[i - 1];
} else {
// 等于的时候
up[i] = up[i - 1];
down[i] = down[i - 1];
}
}
return max(up[length - 1], down[length - 1]);
}
};
直接拿下打败了全世界的人!!!!!!!!!!!!!!!!!!!!!!!!!!
总结
真的是有点难理解,对于我这个刚刚摆烂了十天的人来说,算法已经生疏了,其实老祖宗的话,临阵磨枪不快也光真的是非常的有用好吧。加油