序列问题(leetcode376)

贪心算法之序列问题

376. 摆动序列

本题刚开始的时候可能看的云里雾里的,不知道该怎么下手。但其实我们把序列的峰值图画出来就容易的多了

例如:nums = [1,17,5,10,13,15,10,5,16,8]

我们只需要把单调区间的的部分删除即可

那我们怎么进行判断的呢?

需要找到当前节点是极小值还是极大值,即找到前一段的差值和后一段的差值,两个差值的正负值不同即可。以下为两个差值的定义

int curDiff :当的差值
int preDiff:当前的前一节点的差值

而关于如何判断是否为极值,也很想当然的为if(preDiff > 0 && curDiff < 0 || preDiff < 0 && curDiff > 0) {逻辑操作}

但其实这样并不能包含全部情况,本题需要额外考虑的情况有三种:

  1. 上下坡中有平坡

  2. 数组的两端

  3. 单调坡中有平坡

上下坡中有平坡

如图所示,在这种情况中,我们需要删掉四个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;
    }
}

53. 最大子数组和

本题的核心思想其实和上题是类似的。

当使得累加值小于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;
    }
}

738. 单调递增的数字

本题从逻辑上想起来可能会稍微有点复杂。要保证尽可能的大,而且还要单调,意味着从后往前,只要有一个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;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值