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

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

贪心算法理论基础

讲解:贪心算法理论基础

题目分类:题目分类

贪心的本质是选择每一阶段的局部最优,从而达到全局最优。贪心算法并没有固定的套路,
唯一的难点就是如何通过局部最优推出整体最优。在刷题或者面试的时候,手动模拟一下感觉可以局部最优推出整体最优,而且想不到反例,那么就试一试贪心。

贪心的解题步骤

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

简而言之:常识性推导 + 举反例

455.分发饼干

题目链接:455.分发饼干

贪心算法

思路:本题局部最优就是大饼干喂给胃口大的(也可以反过来,充分利用小饼干),充分利用大饼干尺寸喂饱一个,全局最优就是喂饱尽可能多的小孩,如下图所示:分发饼干
注意:必须先遍历胃口,再遍历饼干,因为外面的 for 循环的 i 是固定移动的,而 if 里面的下标 index 是符合条件才移动的。如果 for 控制的是饼干, if 控制胃口,则会出现如下情况 :遍历顺序
上图表示:if 里的 index 指向胃口 10, for 里的 i 指向饼干 9,因为饼干 9 满足不了胃口 10,所以 i 持续向前移动,而 index 走不到 s[index] >= g[i] 的逻辑,所以 index 不会移动,那么当 i 持续向前移动,最终导致所有的饼干都匹配不上。

class Solution {
    public int findContentChildren(int[] g, int[] s) {
        Arrays.sort(g);
        Arrays.sort(s);
        int count = 0;
        int index = s.length - 1;
        // 注意下方代码:先遍历胃口数组,再对饼干进行判断(代码随想录视频有误)
        // 
        for (int i = g.length - 1; i >= 0; i--) {
            if (index >= 0 && s[index] >= g[i]) {
                count += 1;
                index -= 1;
            }
        }
        return count;
    }
}

376. 摆动序列

题目链接:376. 摆动序列

贪心算法

思路:本题细节较多,详见注释。主要思路为从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。根据题意可将差值抽象为坡度,如下图所示:摆动序列
如图可知,本题的局部最优为删除单调坡度上的节点(不包括单调坡度两端的节点),那么这个坡度就可以有两个局部峰值 ,而全局最优则是整个序列有最多的局部峰值,从而达到最长摆动序列。本题要考虑如下三种情况:

  1. 上下坡中有平坡,对应代码中 if 判断中的 =。
  2. 数组首尾两端,对应代码中假设的与第一个元素相同的“虚拟头元素”。
  3. (易忽略) 单调坡中有平坡,如 [1,2,2,2,3,4],对应代码中仅在坡度变化时,更新prediff 的值(代表坡度的方向),如此 prediff 在 单调区间有平坡的时候就不会发生变化。
class Solution {
    public int wiggleMaxLength(int[] nums) {
    	// 对应题目中一个元素的情况 
        if (nums.length <= 1) {
            return nums.length;
        }
        
        // 当前元素与前一元素的差值,必须初始为 0,
        // 相当于在第一个元素前添加了一个与第一个元素相同的元素,用于适应 for 循环中判断的条件
        int prediff = 0; 
        int curdiff = 0; // 当前差值(可初始化为任意值)
        int  result = 1; // 默认将最后一个元素视为一个摆动元素

		// i 从 1 开始,即先计算第二个元素与第一个元素的差值
		// 因为上方对 prediff 的定义包含假设的与第一个元素相同的“虚拟头元素”
	    for (int i = 1; i < nums.length; i++) {
            curdiff = nums[i] - nums[i - 1];
            // 下方 < 或 > 代表前后差值为一正一负的情况,而 = 代表平坡情况,如/一一一\
            if ((prediff <= 0 && curdiff > 0) || (prediff >= 0 && curdiff < 0)) {
                result++;
                // 仅在坡度变化时,prediff 会跟随curdiff 变化,否则会忽略连续平坡情况,
                // 如 [1,2,2,2,3,4]
                prediff = curdiff;
            }
        }
        return result;
    }
}

53. 最大子序和

题目链接:53. 最大子序和

贪心算法

思路:本题贪心点在于——若目前的子数组和小于 0,即若下一个元素为正数,则当前的和会使该正数变小,所以可以抛弃当前负数和,而将下一个正数作为新和的起点。

  • 局部最优:当前“连续和”为负数的时候立刻放弃,从下一个元素重新计算“连续和”,因为负数加上下一个元素 ,“连续和”只会越来越小。
  • 全局最优:选取最大“连续和”。
class Solution {
    public int maxSubArray(int[] nums) {
        if (nums.length == 1){
            return nums[0];
        }
        int sum = Integer.MIN_VALUE;
        int count = 0;
        for (int i = 0; i < nums.length; i++){
            count += nums[i];
            // // 取区间累计的最大值(相当于不断确定最大子序终止位置)
            sum = Math.max(sum, count); 
            if (count <= 0){
                count = 0; // 相当于重置最大子序起始位置,因为遇到负数一定是拉低总和
            }
        }
       return sum;
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值