贪心算法之序列问题
本题刚开始的时候可能看的云里雾里的,不知道该怎么下手。但其实我们把序列的峰值图画出来就容易的多了
例如:nums = [1,17,5,10,13,15,10,5,16,8]
我们只需要把单调区间的的部分删除即可
那我们怎么进行判断的呢?
需要找到当前节点是极小值还是极大值,即找到前一段的差值和后一段的差值,两个差值的正负值不同即可。以下为两个差值的定义
int curDiff :当的差值 int preDiff:当前的前一节点的差值
而关于如何判断是否为极值,也很想当然的为if(preDiff > 0 && curDiff < 0 || preDiff < 0 && curDiff > 0) {逻辑操作}
但其实这样并不能包含全部情况,本题需要额外考虑的情况有三种:
-
上下坡中有平坡
-
数组的两端
-
单调坡中有平坡
上下坡中有平坡
如图所示,在这种情况中,我们需要删掉四个2中的前三个,第四个2的preDiff = 0 && curDiff < 0;
所以,判断极值的条件语句变成了if(preDiff >= 0 && curDiff < 0 || preDiff <= 0 && curDiff > 0) {逻辑操作}
数组的两端
题目中说过,如果只有两个元素,且两个元素不一样,则摆动序列为2。如:[2, 5]
但是如果我们用前后两个差值进行判断,则至少需要三个节点,我们可以直接把只有两个节点的情况提前判断好,也可以写在后面和判断规则组合在一起。
组合在一起的话,我们可以理解为将[2, 5]变为[2, 2, 5]。这样就不会对结果产生什么影响。
对应的代码码则是让preDiff的初始值为1。整体代码为
public int wiggleMaxLength(int[] nums) { if (nums.length <= 1) { return nums.length; } //当前差值 int curDiff = 0; //上一个差值 int preDiff = 0; int count = 1; for (int i = 1; i < nums.length; i++) { //得到当前差值 curDiff = nums[i] - nums[i - 1]; //如果当前差值和上一个差值为一正一负 //等于0的情况表示初始时的preDiff if ((curDiff > 0 && preDiff <= 0) || (curDiff < 0 && preDiff >= 0)) { count++; } preDiff = curDiff; } return count; }
单调坡度有平坡
例如[1,2,2,2,3,4]
如果是这种情况下,我们上面的代码就会出问题。结果应该是1的,但是上面的代码会输出2。
问题就在于,我们每一次循环的时候都把preDiff更新了,其实只有在出现极值的时候preDiff才需要更新。我们只需要把preDiff = curDiff;
放到if里面即可。
class Solution { public int wiggleMaxLength(int[] nums) { if (nums.length <= 1) { return nums.length; } //当前差值 int curDiff = 0; //上一个差值 int preDiff = 0; int count = 1; for (int i = 1; i < nums.length; i++) { //得到当前差值 curDiff = nums[i] - nums[i - 1]; //如果当前差值和上一个差值为一正一负 //等于0的情况表示初始时的preDiff if ((curDiff > 0 && preDiff <= 0) || (curDiff < 0 && preDiff >= 0)) { count++; preDiff = curDiff; } } return count; } }
本题的核心思想其实和上题是类似的。
当使得累加值小于0的时候,停止累加,并记录这一次累加中最大的累加值。然后开始新一轮的累加。
这么说可能有些抽象了,我来举个栗子。
nums = [-2,1,-3,4,-1,2,1,-5,4]
定义两个变量,int sum = 0;int max = Integer.MIN_VALUE;
。分别用来记录当前的累加值和最大累加值。
nums:-2 1 -3 4 -1 2 1 -5 4
sum: 0 1 0 4 3 5 6 1 5
max: -2 1 1 4 4 5 6 6 6
class Solution { public int maxSubArray(int[] nums) { //只要是正数就一直加,不是正数就结束 int sum = 0; int max = Integer.MIN_VALUE; for (int i = 0; i < nums.length; i++) { sum+=nums[i]; max = Math.max(sum, max); if (sum < 0) { sum = 0; } } return max; } }
本题从逻辑上想起来可能会稍微有点复杂。要保证尽可能的大,而且还要单调,意味着从后往前,只要有一个nums[i-1] > nums[i]
的情况出现,则需要把nums[i]
及其以后的所有数字全部变成9,并且使nums[i-1]--;
。这样才能保证即小于又最大且单调递增。
如:n = 13312
。从后往前到1的时候,发现前一个数字3要比自己大。所以要把3减一变成2,然后让1和2都变成9.就变成了n = 13299
。
然后继续重复此操作,最后得出n = 12999
为题目要求。
其实我们可以把赋值为9的操作放到最后统一进行,只需要用index记录最后一次产生nums[i-1] > nums[i]
的情况出现的索引即可。
int index = nums.length; for (int i = nums.length-1; i > 0 ; i--) { if (nums[i] < nums[i-1]) { nums[i-1]--; index = i; } } for (int i = index; i < nums.length; i++) { chars[i] = 9; }
以上就是这道题的核心代码,但是本题还有一个小问题,题目给的形参是一个int类型的数,而不是一个int[]的数组,我们需要先把他转变成数组类型。
以下为整体代码
class Solution { public int monotoneIncreasingDigits(int n) { //从后往前遍历 如果出现nums[i-1] > nums[i],则让nums[i-1]--,nums[i]=9这样可以保证是最大的。 //先把整数变成数组 String s = String.valueOf(n); char[] chars = s.toCharArray(); int index = chars.length; for (int i = chars.length-1; i > 0 ; i--) { if (chars[i] < chars[i-1]) { chars[i-1]--; index = i; } } for (int i = index; i < chars.length; i++) { chars[i] = '9';//此处不能写成9,会被当成/t } int res = Integer.parseInt(String.valueOf(chars)); return res; } }