376.摆动序列
376. 摆动序列
中等
如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为 摆动序列 。第一个差(如果存在的话)可能是正数或负数。仅有一个元素或者含两个不等元素的序列也视作摆动序列。
-
例如,
[1, 7, 4, 9, 2, 5]
是一个 摆动序列 ,因为差值(6, -3, 5, -7, 3)
是正负交替出现的。 - 相反,
[1, 4, 7, 2, 5]
和[1, 7, 4, 5, 5]
不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。
子序列 可以通过从原始序列中删除一些(也可以不删除)元素来获得,剩下的元素保持其原始顺序。
给你一个整数数组 nums
,返回 nums
中作为 摆动序列 的 最长子序列的长度 。
示例 1:
输入:nums = [1,7,4,9,2,5] 输出:6 解释:整个序列均为摆动序列,各元素之间的差值为 (6, -3, 5, -7, 3) 。
示例 2:
输入:nums = [1,17,5,10,13,15,10,5,16,8] 输出:7 解释:这个序列包含几个长度为 7 摆动序列。 其中一个是 [1, 17, 10, 13, 10, 16, 8] ,各元素之间的差值为 (16, -7, 3, -3, 6, -8) 。
示例 3:
输入:nums = [1,2,3,4,5,6,7,8,9] 输出:2
提示:
1 <= nums.length <= 1000
0 <= nums[i] <= 1000
进阶:你能否用 O(n)
时间复杂度完成此题?
class Solution { // 定义一个公共方法wiggleMaxLength,接收一个整数数组nums作为参数,返回摆动长度 public int wiggleMaxLength(int[] nums) { // 如果数组长度小于等于1,则摆动长度等于数组长度 if (nums.length <= 1) { return nums.length; } // 当前差值,初始化为0 // 表示当前元素与前一个元素的差值 int curDiff = 0; // 上一个差值,初始化为0 // 表示前一个元素与前一个元素的差值 // 在第一次循环时,preDiff实际上是一个“初始值”,用于比较 int preDiff = 0; // 摆动长度,初始化为1 // 因为至少有一个元素,所以摆动长度至少为1 int count = 1; // 从数组的第二个元素开始遍历 for (int i = 1; i < nums.length; i++) { // 计算当前差值 curDiff = nums[i] - nums[i - 1]; // 判断当前差值与上一个差值的符号是否相反 // 如果相反,说明摆动长度增加 // 注意:curDiff > 0 && preDiff <= 0 表示当前差值为正,且上一个差值为负或零 // curDiff < 0 && preDiff >= 0 表示当前差值为负,且上一个差值为正或零 if ((curDiff > 0 && preDiff <= 0) || (curDiff < 0 && preDiff >= 0)) { count++; // 摆动长度增加 preDiff = curDiff; // 更新上一个差值为当前差值 } } // 返回摆动长度 return count; }
}
要求删除元素使其达到最大摆动序列,应该删除什么元素呢?
局部最优:删除单调坡度上的节点(不包括单调坡度两端的节点),那么这个坡度就可以有两个局部峰值。
整体最优:整个序列有最多的局部峰值,从而达到最长摆动序列。
实际操作上,其实连删除的操作都不用做,因为题目要求的是最长摆动子序列的长度,所以只需要统计数组的峰值数量就可以了
在计算是否有峰值的时候,大家知道遍历的下标 i ,计算 prediff(nums[i] - nums[i-1]) 和 curdiff(nums[i+1] - nums[i]),如果prediff < 0 && curdiff > 0
或者 prediff > 0 && curdiff < 0
此时就有波动就需要统计。
这是我们思考本题的一个大题思路,但本题要考虑三种情况:
- 情况一:上下坡中有平坡
- 情况二:数组首尾两端
- 情况三:单调坡中有平坡
情况一:上下坡中有平坡
例如 [1,2,2,2,1]这样的数组,如图:
它的摇摆序列长度是多少呢? 其实是长度是 3,也就是我们在删除的时候 要不删除左面的三个 2,要不就删除右边的三个 2。
如图,可以统一规则,删除左边的三个 2:
在图中,当 i 指向第一个 2 的时候,prediff > 0 && curdiff = 0
,当 i 指向最后一个 2 的时候 prediff = 0 && curdiff < 0
。
如果我们采用,删左面三个 2 的规则,那么 当 prediff = 0 && curdiff < 0
也要记录一个峰值,因为他是把之前相同的元素都删掉留下的峰值。
所以我们记录峰值的条件应该是: (preDiff <= 0 && curDiff > 0) || (preDiff >= 0 && curDiff < 0)
,为什么这里允许 prediff == 0 ,就是为了 上面我说的这种情况。
情况二:数组首尾两端
所以本题统计峰值的时候,数组最左面和最右面如何统计呢?
题目中说了,如果只有两个不同的元素,那摆动序列也是 2。
例如序列[2,5],如果靠统计差值来计算峰值个数就需要考虑数组最左面和最右面的特殊情况。
因为我们在计算 prediff(nums[i] - nums[i-1]) 和 curdiff(nums[i+1] - nums[i])的时候,至少需要三个数字才能计算,而数组只有两个数字。
不写死的话,如何和我们的判断规则结合在一起呢?
可以假设,数组最前面还有一个数字,那这个数字应该是什么呢?
那么为了规则统一,针对序列[2,5],可以假设为[2,2,5],这样它就有坡度了即 preDiff = 0(即将preDif初始化为0),如图:
针对以上情形,result 初始为 1(默认最左面有一个峰值),此时 curDiff > 0 && preDiff <= 0,那么 result++(计算了左面的峰值),最后得到的 result 就是 2(峰值个数为 2 即摆动序列长度为 2)
情况三:单调坡度有平坡
在版本一中,我们忽略了一种情况,即 如果在一个单调坡度上有平坡,例如[1,2,2,2,3,4],如图:
图中,我们可以看出,版本一的代码在三个地方记录峰值,但其实结果因为是 2,因为 单调中的平坡 不能算峰值(即摆动)。
之所以版本一会出问题,是因为我们实时更新了 prediff。
那么我们应该什么时候更新 prediff 呢?
我们只需要在 这个坡度 摆动变化的时候,更新 prediff 就行,这样 prediff 在 单调区间有平坡的时候 就不会发生变化,造成我们的误判。
738.单调递增的数字
738. 单调递增的数字
中等
提示
当且仅当每个相邻位数上的数字 x
和 y
满足 x <= y
时,我们称这个整数是单调递增的。
给定一个整数 n
,返回 小于或等于 n
的最大数字,且数字呈 单调递增 。
示例 1:
输入: n = 10 输出: 9
示例 2:
输入: n = 1234 输出: 1234
示例 3:
输入: n = 332 输出: 299
提示:
0 <= n <= 109
思路是在原来给出的数字中处理,从后向前遍历,如果遇到前一个数大于后一个数的情况,就将该前一个数减1,将后面的数字都改为9,这样既保持了递增,又使得最后的结果最大
class Solution { public int monotoneIncreasingDigits(int n) { // 将整数n转换为字符串 String s = String.valueOf(n); // 将字符串转换为字符数组,方便进行字符操作 char[] sChar = s.toCharArray(); // 初始化start为数组长度,表示还没有找到需要修改的位置 int start = sChar.length; // 从数组倒数第二个字符开始向前遍历 for(int i = sChar.length - 2; i >= 0; i--){ // 如果当前字符大于下一个字符,说明不满足单调递增的条件 if(sChar[i] > sChar[i + 1]){ // 更新start为当前位置+1,表示从当前位置开始后面的字符都需要变为9 start = i + 1; // 将当前字符减1,以便让后续字符满足单调递增的条件 sChar[i] -= 1; } } // 从start位置开始,将后面的所有字符都变为'9',以满足单调递增的条件 for(int i = start; i < sChar.length; i++){ sChar[i] = '9'; } // 将修改后的字符数组转换回整数 int num = Integer.parseInt(String.valueOf(sChar)); // 返回修改后的整数 return num; }
}
题目要求小于等于N的最大单调递增的整数,那么拿一个两位的数字来举例。
例如:98,一旦出现strNum[i - 1] > strNum[i]的情况(非单调递增),首先想让strNum[i - 1]--,然后strNum[i]给为9,这样这个整数就是89,即小于98的最大的单调递增整数。
这一点如果想清楚了,这道题就好办了。
此时是从前向后遍历还是从后向前遍历呢?
从前向后遍历的话,遇到strNum[i - 1] > strNum[i]的情况,让strNum[i - 1]减一,但此时如果strNum[i - 1]减一了,可能又小于strNum[i - 2]。
这么说有点抽象,举个例子,数字:332,从前向后遍历的话,那么就把变成了329,此时2又小于了第一位的3了,真正的结果应该是299。
那么从后向前遍历,就可以重复利用上次比较得出的结果了,从后向前遍历332的数值变化为:332 -> 329 -> 299
确定了遍历顺序之后,那么此时局部最优就可以推出全局,找不出反例,试试贪心。