算法第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;
    }
};
相关推荐
江畔柳前堤7 小时前
大语言模型分布式训练:从并行策略到万卡工程的系统梳理
人工智能·分布式·深度学习·算法·目标检测·机器学习·语言模型
Forever Nore8 小时前
LeetCode 4 寻找两个正序数组的中位数 - 二分
算法·leetcode
罗西的思考9 小时前
【OpenClaw具身硬件】MiniClaw 阅读笔记---(1)基础
人工智能·算法·机器学习
蛋先生DX10 小时前
大模型参数存储格式揭秘:BF不是男朋友
深度学习·算法·llm
爱跳舞的烤冷面10 小时前
自学嵌入式第22天(数据结构——哈希)
数据结构·算法·哈希算法
猎嘤一号11 小时前
博弈论(Game Theory)的理论、算法与工程
人工智能·算法·安全·博弈论
月光船幽幽11 小时前
影子模式下保护 logits 不被修改
人工智能·python·算法
马拉AI11 小时前
腾讯开源 Agent 记忆系统,AI“换对话就忘”的问题有了新解法(附安装使用教程)
人工智能·算法·开源·科研
地平线开发者13 小时前
征程6工具链模型X86推理方式说明
算法
地平线开发者13 小时前
【征程6】校准量化中HistogramObserver解析
算法