算法打卡day27|贪心算法篇01|Leetcode 455.分发饼干、376. 摆动序列、53. 最大子序和

目录

贪心算法理论基础

定义

使用场景

一般解题步骤

算法题

Leetcode 455.分发饼干

 个人思路

解法

贪心法

 Leetcode  376. 摆动序列

个人思路

解法

贪心法

 Leetcode  53. 最大子序和

 个人思路

解法

贪心法


贪心算法理论基础

定义

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

例如,有一堆不同数值的钞票,可以拿走十张,如果想达到最大的金额可以指定每次拿最大的,最终结果就是拿走最大数额的钱。

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

使用场景

贪心算法并没有固定的套路,唯一的难点就是如何通过局部最优,推出整体最优

题目是否能用贪心,得靠手动模拟实例,如果举不到反例代表模拟可行,就可以试一试贪心策略,如果不可行,可能需要动态规划

一般解题步骤

贪心算法一般分为如下四步:

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

但做题的时候,只要想清楚 局部最优 是什么,如果推导出全局最优,那解题思路就到了,贪心没有套路,就是常识性推导加上举反例

算法题

Leetcode 455.分发饼干

题目链接:455.分发饼干

 大佬视频讲解:分发饼干视频讲解

 个人思路

有不同胃口的小孩和不同规格的饼干,可以优先用大饼干满足大胃口的孩子,,最后能得到最多可以满足几个孩子胃口.这样得到局部最优从而推出全局最优,可以用贪心法

解法
贪心法

优先考虑胃口,先喂饱大胃口.先将饼干数组和小孩数组排序。然后从后向前遍历小孩数组,用大饼干优先满足胃口大的,并统计满足小孩数量.

class Solution {
    public int findContentChildren(int[] g, int[] s) {
        Arrays.sort(g);//排序
        Arrays.sort(s);
        int count = 0;
        int start = s.length - 1;//饼干数组长度
       
        for (int index = g.length - 1; index >= 0; index--) { // 遍历胃口
            if(start >= 0 && g[index] <= s[start]) {//从后往前遍历:先满足大胃口的
                start--;
                count++;
            }
        }
        return count;
    }
}

时间复杂度:O(nlogn);(排序操作的时间复杂度通常是O(n log n),遍历是n,取最大)

空间复杂度:O(n);(排序操作在最坏情况下可能需要O(n)的额外空间)


 Leetcode  376. 摆动序列

题目链接:376. 摆动序列

大佬视频讲解:摆动序列视频讲解

个人思路

思路有点混乱...

解法
贪心法

首先搞清楚如何修改数组,因为要求删除元素使其达到最大摆动序列,应该删除单调上多余的节点

用示例二来举例,如图所示:

局部最优:删除单调坡度上的节点(不包括单调坡度两端的节点),那么这个坡度就可以有两个局部峰值整体最优:整个序列有最多的局部峰值,从而达到最长摆动序列

局部最优推出全局最优,并举不出反例,那可以贪心!

因为题目要求的是最长摆动子序列的长度,所以只需要统计数组的峰值数量,然后删除单一坡度上的节点

先加两个变量,curdiff 和 prediff 计算当前和前一个的峰值情况

在计算是否有峰值的时候,需要要考虑三种情况:

情况一:上下坡中有平坡

例如 [1,3,3,3,1]这样的数组,如图:

这里的摇摆序列长度是是 3,可以统一规则,删除左边的三个 2;

在遍历时,当 i 指向第一个 2 的时候,prediff > 0 && curdiff = 0 ;当 i 指向最后一个 2 的时候 prediff = 0 && curdiff < 0;删除左边的三个 2后, prediff = 0 && curdiff < 0 也要记录一个峰值,因为这是把之前相同的元素都删掉留下的峰值

所以记录峰值的条件应该是: (preDiff <= 0 && curDiff > 0) || (preDiff >= 0 && curDiff < 0)

这里允许 prediff == 0 ,就是上下坡中有平坡这种情况。

情况二:数组首尾两端

这种情况需要考虑数组最左面和最右面,如下图;

针对这种情况,result 初始为 1(默认最右面有一个峰值),此时 curDiff > 0 && preDiff <= 0,那么 result++(计算了左面的峰值),最后得到的 result 就是 2(峰值个数为 2摆动序列长度为 2

情况三:单调坡中有平坡

在一个单调坡度上有平坡,例如[1,3,3,3,4,6]

如果只考虑上面两种情况,那么实时更新了 prediff就会有问题。

所以需要修改更新配prediff的时机,改为这个坡度 摆动变化的时候,更新 prediff 就行,这样 prediff 在 单调区间有平坡的时候 就不会发生变化,造成误判。

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;//更新preDiff
            }
        }
        return count;
    }
}

时间复杂度:O(n);(遍历整个数组)

空间复杂度:O(1);(常量级的变量)


 Leetcode  53. 最大子序和

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

大佬视频讲解:最大子序和视频讲解

 个人思路

因为局部最优的情况下,并记录最大的“连续和”,可以推出全局最优,所以可以用贪心。

解法
贪心法

遍历数组,累加和,当前“连续和”为负数的时候立刻放弃,从下一个元素重新计算“连续和”,因为负数加上下一个元素 “连续和”只会越来越小.

这相当于是暴力解法中的不断调整最大子序和区间的起始位置

class Solution {
    public int maxSubArray(int[] nums) {
        if (nums.length == 1){
            return nums[0];
        }

        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;
    }
}

时间复杂度:O(n);(遍历整个数组)

空间复杂度:O(1);(常量级变量)


 以上是个人的思考反思与总结,若只想根据系列题刷,参考卡哥的网址代码随想录算法官网

  • 37
    点赞
  • 24
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值