算法第23天|贪心算法:基础理论、分发饼干、摆动序列、最大子序和

今日总结:

摆动序列的三种特殊情况需要着重思考,感觉是没有思考清楚

基础理论

1、贪心的本质:

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

例如:一堆钞票,只能拿走10张,如何拿走最多的金额?:每次拿最大的(局部最优),最后就是拿走最多的金额(全局最优)

2、 贪心的套路:

贪心算法没有固定的套路。

难点:如何通过局部最优推理出全局最优(也没有具体的套路)

如何验证可不可以使用贪心算法:

举反例,如果想不到反例就可以尝试使用贪心算法

3、贪心的一般解题思路(鸡肋,实际做题不能按照这个考虑):

(1)将问题分解为若干个子问题

(2)找出适合的贪心策略

(3)求解每一个子问题的最优解

(4)将局部最优解堆叠成全局最优解

实际做题中,只需要考虑局部最优是什么,如何推导出全局最优

分发饼干

题目链接:455. 分发饼干 - 力扣(LeetCode)

总体思路:

将每一块饼干都能够给到适合的孩子,就能达到尽可能多的孩子。

所以,将饼干从小到大排序, 对孩子的胃口值也从小到大排序,尽量将每一块小饼干都能分给对应的小孩子。

总体代码:

cpp 复制代码
class Solution {
public:
    int findContentChildren(vector<int>& g, vector<int>& s) {
        //对两者都进行排序
        sort(g.begin(),g.end());
        sort(s.begin(),s.end());
        //一个记录分配的变量
        int sum=0;
        //遍历饼干,从最小的胃口值开始分配
        for(int i=0,j=0;j<s.size();j++)
        {
            if(i<g.size()&&s[j]>=g[i])
            {
                //满足胃口值,满足局部最合适,进行下一个孩子,同时记录
                i++;
                sum++;
            }
        }
        return sum;
    }
};

摆动序列

题目链接:376. 摆动序列 - 力扣(LeetCode)

总体思路:

1、首先理解摆动序列的含义:连续数字之间的差严格的在正数和负数之间交替,则这个数字序列称为摆动序列,少于两个元素的序列也是摆动序列。

2、目标:

(1)现在给定的序列是一个未知的序列:

(2)需要通过删除最少的元素去获得最大的摆动序列,

(3)且不能改变元素的原本位置。

3、方法:贪心算法:通过局部最优推导全局最优

(1)局部最优:删除单调坡度上的节点(顶、谷不删除),那么这个坡度就有两个局部峰值。

(2)全局最优:整个序列拥有最多的局部峰值--->达到最长的摆动序列

4、寻找峰值情况的讨论

(1)上下坡中有平坡

需要删除(不统计)平坡的前边,只保留最后一个峰或者谷[i]-[i-1]<=0&&[i+1]-[i]>0或者[i]-[i-1]>=0&&[i+1]-[i]<0才记录

(2)数组首尾两端的记录方法

因为现在讨论的是通过当前的点与上一个点、下一个点的差获得当前是不是峰谷,所以对于数组首尾的特殊(首没有前一个元素,尾没有下一个元素),可以通过假设数组前还有一个与首相同的元素,即默认峰值是1开始计算。

相当于提前记录一个左边的端点,去记录的第二个值是左端点与右端点的峰,没有记录右端点

(3)单调坡中有平坡

在第(1)种中讨论了上下坡中有平坡,处理方法是不去记录前边平的位置,但是在单调的坡中如果有平坡,可能会记录上平坡的右边位置,导致位置变多,所以需要在每次获取到当前位置的左右坡度后,需要将左边的坡度=右边的坡度,在下次计算的时候就会处理掉单调坡有平坡的现象。

相当于只记录峰值变化,只要没有出现峰值,就不去记录,也就是将前一个峰值的坡度记录下来,只要当前坡度不相反,就不去记录。

总体代码:

cpp 复制代码
class Solution {
public:
    //核心寻找局部最优,即不记录坡度中的值,只记录峰值
    //需要注意三点:
    //(1)坡度有平坡的现象
    //(2)对于起始与结束的位置(因为要三个点比较)
    //(3)对于单调的坡中存在平坡现象
    
    
    int wiggleMaxLength(vector<int>& nums) {
        int length =1;//记录长度,从1开始,因为除去起始位置的默认坡度
        int pre=0;
        int cur=0;//定义当前的坡度
        for(int i=0;i<nums.size()-1;i++)//因为默认左端点前有一个与左端点一样的值,不去记录右端点的位置了
        {   
            //使用cur去更新pre,从而避免单调坡中出现平坡的问题
            if(nums.size()<1)return length;
            //记录当前的坡度
            cur = nums[i+1]-nums[i];
            if((pre<=0&&cur>0)||(pre>=0&&cur<0))
            {
                length++;
                //更新前一个坡度,只有在记录峰值的时候才会更新坡度,不是每次计算cur都更新前一个的坡度
                pre = cur;
            }
        }
        return length;
        
    }
};

最大子序和

题目链接:53. 最大子数组和 - 力扣(LeetCode)

总体思路:

序列中存在正数、负数,求这个序列中的连续的子序列最大的和:所以要着重注意负数的影响,因为正数总是将和增大,负数会将和减小

如果从某个值开始的子序列的和为负数了:说明当前值是一个负数,且与之前子序列的和加起来都要小于0,这个数只会影响整体的和,所以直接跳过当前的这个负数,从下一个值重新记录子序列的和。

但是如果只是加上一个小的负数,整体结果仍旧大于0,不能重新开始记录,因为后边如果有大的正数,会使整体子序列的和变得更大。

所以,只有当前子序列的和为负数的时候,才从当前值的下一个值开始记录子序列的和,也就是贪心思想,局部的最大,推导出全局的最大。

总体代码:

cpp 复制代码
class Solution {
public:
    //最大和肯定和负值有关系,因为正值只会增加和
    //当目前的和小于0,就要舍弃,从新计算连续子序列的和(当前负数比之前所有的数之和都小,一定不能带这个负数,不如后边的自己相加)
    int maxSubArray(vector<int>& nums) {
        int sum = INT_MIN;//记录最大的和
        int cur_sum = 0;//记录当前的和
        for(int i=0;i<nums.size();i++)
        {
            cur_sum += nums[i];
            //判断当前的子序列的和是不是大于sum
            sum = sum >cur_sum?sum : cur_sum;
            if(cur_sum<0) cur_sum=0;
        }
        return sum;
    }
};
相关推荐
秋说9 分钟前
【PTA数据结构 | C语言版】根据层序序列重构二叉树
c语言·数据结构·算法
秋说1 小时前
【PTA数据结构 | C语言版】前序遍历二叉树
c语言·数据结构·算法
会唱歌的小黄李2 小时前
【算法】贪心算法:最大数C++
c++·算法·贪心算法
NuyoahC2 小时前
笔试——Day8
c++·算法·笔试
墨染点香2 小时前
LeetCode Hot100 【1.两数之和、2.两数相加、3.无重复字符的最长子串】
算法·leetcode·职场和发展
秋说3 小时前
【PTA数据结构 | C语言版】二叉树层序序列化
c语言·数据结构·算法
地平线开发者3 小时前
开发者说|Aux-Think:为什么测试时推理反而让机器人「误入歧途」?
算法·自动驾驶
Tiny番茄3 小时前
46. 携带研究材料(01背包二维数组)
算法·动态规划
共享家95274 小时前
排序算法实战(上)
数据结构·算法·排序算法
千楼4 小时前
LeetCode 1888. 使二进制字符串字符交替的最少反转次数
算法·leetcode·职场和发展