代码随想录算法训练营第三十一天|455.分发饼干、376. 摆动序列、53. 最大子序和

114 阅读2分钟

贪心理论基础

什么是贪心

贪心的本质就是选择每一阶段的局部最优解,从而达到全局的最优解

例如,有一堆钞票,你可以拿走十张,如果想达到最大的金额,你要怎么拿?

指定每次拿最大的,最终结果就是拿走最大数额的钱。

每次拿最大的就是局部最优,最后拿走最大数额的钱就是推出全局最优。

再举一个例子如果是 有一堆盒子,你有一个背包体积为n,如何把背包尽可能装满,如果还每次选最大的盒子,就不行了。这时候就需要动态规划。

什么时候使用贪心

贪心的题目一般不容易一眼看出来使用贪心,贪心解题也没有特别固定的方法。当我们看到题目,感觉可以通过局部最优来推出全局最优,并且找不到反例,就可以尝试使用贪心来解决问题。

贪心的一般解题步骤

贪心的代码实现上并没有固定的解题步骤,在思路上我们可以分为以下四步:

  • 将问题分解为若干个子问题
  • 找出适合的贪心策略
  • 求解每一个子问题的最优解
  • 将局部最优解堆叠成全局最优解

455.分发饼干

题目链接:455. 分发饼干

思路:这里的局部最优解就是小饼干先分给胃口小的,充分利用饼干的尺寸,全局最优解就是喂饱小孩的个数(也可以是大饼干优先分给胃口大的)。

class Solution {
    public int findContentChildren(int[] g, int[] s) {
        // 对数组排序,方便充分利用饼干的尺寸。
        Arrays.sort(g);
        Arrays.sort(s);
        int index = 0;
        for (int i = 0; i < s.length; i++) {
            if (index < g.length && g[index] <= s[i]) index++; // 喂饱一个就再判断下一个。
        }
        return index;
    }
}

376. 摆动序列

题目链接:376. 摆动序列

思路:采用贪心算法。这里的局部最优可以理解为删除单调坡上的节点,使得当前坡度的峰值最优。全局最优就是整个序列峰值最多。

image.png

class Solution {
    public int wiggleMaxLength(int[] nums) {
        int maxLen = 1;
        if (nums.length == 1) return maxLen;
        boolean flag = false;
        int j = 1;
        while (j < nums.length && nums[j] == nums[j - 1]) j++;
        if (j == nums.length) return maxLen;
        if (nums[j] - nums[j - 1] > 0) {
            flag = true;
        } else if (nums[j] - nums[j - 1] < 0) {
            flag = false;
        }
        maxLen++;
        for (int i = 2; i < nums.length; i++) {
            if (flag) {
                if (nums[i] - nums[i - 1] < 0) {
                    maxLen++;
                    flag = false;
                }
            } else {
                if (nums[i] - nums[i - 1] > 0) {
                    maxLen++;
                    flag = true;
                }
            }
        }
        return maxLen;
    }
}

随想录网站上解法,思路相同,代码更加整洁

class Solution {
    public int wiggleMaxLength(int[] nums) {
        int maxLen = 1;
        if (nums.length == 1) return maxLen;
        boolean flag = false;
        int j = 1;
        while (j < nums.length && nums[j] == nums[j - 1]) j++;
        if (j == nums.length) return maxLen;
        if (nums[j] - nums[j - 1] > 0) {
            flag = true;
        } else if (nums[j] - nums[j - 1] < 0) {
            flag = false;
        }
        maxLen++;
        for (int i = 2; i < nums.length; i++) {
            if (flag) {
                if (nums[i] - nums[i - 1] < 0) {
                    maxLen++;
                    flag = false;
                }
            } else {
                if (nums[i] - nums[i - 1] > 0) {
                    maxLen++;
                    flag = true;
                }
            }
        }
        return maxLen;
    }
}

本题也可以使用动态规划来解决。

53. 最大子序和

题目链接:53. 最大子数组和

思路:使用贪心算法。

局部最优:当前“连续和”为负数的时候立刻放弃,从下一个元素重新计算“连续和”,因为负数加上下一个元素 “连续和”只会越来越小。

全局最优:选取最大“连续和”

这里最大的局部最优就是全局最优。

53.最大子序和.gif

class Solution {
    public int maxSubArray(int[] nums) {
        int res = Integer.MIN_VALUE;
        int count = 0;
        for (int i = 0; i < nums.length; i++) {
            count += nums[i];
            if (count > res) res = count; 
            if (count < 0) count = 0; // 如果遇到负数,重新计算局部连续和,因为负数一定是拉低总和的
        }
        return res;
    }
}