代码随想录-刷题第三十二天

贪心理论基础

什么是贪心

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

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

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

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

再举一个例子如果是 有一堆盒子,你有一个背包体积为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;
    }
}

另一种解法:优先考虑胃口,先喂饱大胃口

// 时间复杂度:O(nlogn)
// 空间复杂度:O(1)
class Solution {
    public int findContentChildren(int[] g, int[] s) {
        Arrays.sort(g);
        Arrays.sort(s);
        int res = 0;
        int index = s.length - 1;  // 饼干数组的下标

        for (int i = g.length - 1; i >= 0; i--) {  // 遍历胃口
            if(index >= 0 && s[index] >= g[i]) {  // 遍历饼干
                res++;
                index--;
            }
        }
        return res;
    }
}

用了一个index来控制饼干数组的遍历,遍历饼干并没有再起一个for循环,而是采用自减的方式,这也是常用的技巧。

不能看到要遍历两个数组,就想到用两个for循环,那样逻辑其实就复杂了。


376. 摆动序列

题目链接:376. 摆动序列

思路:采用贪心算法。题解链接

376.摆动序列

**局部最优:**删除单调坡度上的节点(不包括单调坡度两端的节点),那么这个坡度就可以有两个局部峰值。

**整体最优:**整个序列有最多的局部峰值,从而达到最长摆动序列。

在计算是否有峰值的时候,大家知道遍历的下标i ,计算prediff(nums[i] - nums[i-1]) 和 curdiff(nums[i+1] - nums[i]),如果prediff < 0 && curdiff > 0 或者 prediff > 0 && curdiff < 0 此时就有波动就需要统计。

这是思考本题的一个大体思路,但本题要考虑三种情况:

  1. 情况一:上下坡中有平坡
  2. 情况二:数组首尾两端
  3. 情况三:单调坡中有平坡
class Solution {
    public int wiggleMaxLength(int[] nums) {
        if (nums.length <= 1) {
            return nums.length;
        }
        int curDiff = 0;  // 当前差值
        int preDiff = 0;  // 前一对差值
        int res = 1;    // 记录峰值个数,序列默认序列最右边有一个峰值
        for (int i = 1; i < nums.length; i++) {
            // 得到当前差值
            curDiff = nums[i] - nums[i - 1];
            // 出现峰值
            if ((preDiff <= 0 && curDiff > 0) || (preDiff >= 0 && curDiff < 0)) {
                res++;
                // 注意这里,只在摆动变化的时候更新preDiff
                preDiff = curDiff;
            }
        }
        return res;
    }
}

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


53. 最大子数组和

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

思路:使用贪心算法。

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

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

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

如动画所示:

53.最大子序和

红色的起始位置就是贪心每次取count为正数的时候,开始一个区间的统计。

都知道负数加上正数之后会变小,但是这道题目依然会让很多人搞混淆,其关键在于:不能让“连续和”为负数的时候加下一个元素,而不是不让“连续和”加一个负数

class Solution {
    public int maxSubArray(int[] nums) {
        int res = Integer.MIN_VALUE;
        int sum = 0;
        for (int i = 0; i < nums.length; i++) {
            sum += nums[i];
            // 取区间累计的最大值(相当于不断确定最大子序终止位置)
            res = Math.max(res, sum);
            // 相当于重置最大子序起始位置,因为遇到连续和为负数一定是拉低总和
            if (sum <= 0) {
                sum = 0;
            }
        }
        return res;
    }
}

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


  • 20
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值