代码随想录算法训练营第三十一天 | 贪心1,想不到怎么找局部最优就做不出来

news2024/11/24 0:01:19

贪心算法理论基础

代码随想录 (programmercarl.com)

贪心算法理论基础!_哔哩哔哩_bilibili

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

    例如,有一堆钞票,你可以拿走十张,如果想达到最大的金额,你要怎么拿?指定每次拿最大的,最终结果就是拿走最大数额的钱。每次拿最大的就是局部最优,最后拿走最大数额的钱就是推出全局最优。

  • 有没有什么套路可以一看就看出来是贪心

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

  • 如何能看出局部最优是否能推出整体最优呢?有没有什么固定策略或者套路呢?

    也没有! 靠自己手动模拟,如果模拟可行,就可以试一试贪心策略,如果不可行,可能需要动态规划。

  • 如何验证可不可以用贪心算法呢?

    最好用的策略就是举反例,如果想不到反例,那么就试一试贪心吧

面试中基本不会让面试者现场证明贪心的合理性,代码写出来跑过测试用例即可,或者自己能自圆其说理由就行了

刷题或者面试的时候,手动模拟一下感觉可以局部最优推出整体最优,而且想不到反例,那么就试一试贪心

贪心一般解题步骤

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

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

这个四步其实过于理论化了,我们平时在做贪心类的题目 很难去按照这四步去思考,真是有点“鸡肋”。

做题的时候,只要想清楚 局部最优 是什么,如果推导出全局最优,其实就够了。

455.分发饼干

文档讲解:代码随想录 (programmercarl.com)

视频讲解:贪心算法,你想先喂哪个小孩?| LeetCode:455.分发饼干_哔哩哔哩_bilibili

状态:能直接做出来。文档提供了两种思路,我用的是思路2。

思路1

为了满足更多的小孩,就不要造成饼干尺寸的浪费。

大尺寸的饼干既可以满足胃口大的孩子也可以满足胃口小的孩子,那么就应该优先满足胃口大的。

这里的局部最优就是大饼干喂给胃口大的,充分利用饼干尺寸喂饱一个,全局最优就是喂饱尽可能多的小孩

可以尝试使用贪心策略,先将饼干数组和小孩数组排序。

然后从后向前遍历小孩数组,用大饼干优先满足胃口大的,并统计满足小孩数量。

// 时间复杂度:O(nlogn)
// 空间复杂度:O(1)
class Solution {
public:
    int findContentChildren(vector<int>& g, vector<int>& s) {
        sort(g.begin(), g.end());
        sort(s.begin(), s.end());
        int index = s.size() - 1; // 饼干数组的下标
        int result = 0;
        for (int i = g.size() - 1; i >= 0; i--) { // 遍历胃口 
            if (index >= 0 && s[index] >= g[i]) { // 遍历饼干 
                result++;
                index--;
            }
        }
        return result;
    }
};

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

注意事项

上面代码中,可以看出来,是先遍历的胃口,在遍历的饼干,那么可不可以 先遍历 饼干,在遍历胃口呢?其实是不可以的。

外面的for 是里的下标i 是固定移动的,而if里面的下标 index 是符合条件才移动的。若 for 控制的是饼干, if 控制胃口,就出现如下情况

在这里插入图片描述

if 里的 index 指向 胃口 10, for里的i指向饼干9,因为 饼干9 满足不了 胃口10,所以 i 持续向前移动,而index 走不到s[index] >= g[i] 的逻辑,所以index不会移动,那么当i 持续向前移动,最后所有的饼干都匹配不上。

所以 一定要for 控制 胃口,里面的if控制饼干。

思路2

小饼干先喂饱小胃口

class Solution {
public:
    int findContentChildren(vector<int>& g, vector<int>& s) {
        sort(g.begin(),g.end());
        sort(s.begin(),s.end());
        int index = 0;
        for(int i = 0; i < s.size(); i++) { // 饼干
            if(index < g.size() && g[index] <= s[i]){ // 胃口
                index++;
            }
        }
        return index;
    }
};

这种写法,两个循环的顺序改变了,先遍历的饼干,在遍历的胃口,这是因为遍历顺序变了,我们是从小到大遍历。

理由在上面 “注意事项”中 已经讲过。

376. 摆动序列

文档讲解:代码随想录 (programmercarl.com)

视频讲解:贪心算法,寻找摆动有细节!| LeetCode:376.摆动序列_哔哩哔哩_bilibili

状态:做不出来。很难,情况很多。

思路

来分析一下,要求删除元素使其达到最大摆动序列,应该删除什么元素呢?用示例二来举例,如图所示:

在这里插入图片描述

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

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

实际操作上,其实连删除的操作都不用做,因为题目要求的是最长摆动子序列的长度,所以只需要统计数组的峰值数量就可以了

这就是贪心所贪的地方,让峰值尽可能的保持峰值,然后删除单一坡度上的节点

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

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

  1. 情况一:上下坡中有平坡
  2. 情况二:数组首尾两端
  3. 情况三:单调坡中有平坡

情况一:上下坡中有平坡

在这里插入图片描述

它的摇摆序列长度是多少呢? 其实是长度是3,也就是我们在删除的时候 要不删除左面的三个2,要不就删除右边的三个2。

如图,可以统一规则,删除左边的三个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)

情况二:数组首尾两端

数组最左面和最右面如何统计呢?

例如序列[2,5],如果靠统计差值来计算峰值个数就需要考虑数组最左面和最右面的特殊情况。

因为我们在计算 prediff(nums[i] - nums[i-1]) 和 curdiff(nums[i+1] - nums[i])的时候,至少需要三个数字才能计算,而数组只有两个数字。

这里我们可以写死,就是 如果只有两个元素,且元素不同,那么结果为2。

不写死的话,如果和我们的判断规则结合在一起呢?

可以假设,数组最前面还有一个数字,那这个数字应该是什么呢?

之前我们在 讨论 情况一:相同数字连续 的时候, prediff = 0 ,curdiff < 0 或者 >0 也记为波谷。

那么为了规则统一,针对序列[2,5],可以假设为[2,2,5],这样它就有坡度了即preDiff = 0,如图:

在这里插入图片描述

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

经过以上分析后,我们可以写出如下代码:

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        if (nums.size() <= 1) return nums.size();
        int curDiff = 0; // 当前一对差值
        int preDiff = 0; // 前一对差值
        int result = 1;  // 记录峰值个数,序列默认序列最右边有一个峰值
        for (int i = 0; i < nums.size() - 1; i++) {
            curDiff = nums[i + 1] - nums[i];
            // 出现峰值
            if ((preDiff <= 0 && curDiff > 0) || (preDiff >= 0 && curDiff < 0)) {
                result++;
            }
            preDiff = curDiff;
        }
        return result;
    }
};

情况三:单调坡度有平坡

在这里插入图片描述

图中,我们可以看出,上面的代码在三个地方记录峰值,但其实结果因为是2,因为 单调中的平坡 不能算峰值(即摆动)。

之所以会出问题,是因为我们实时更新了 prediff。

那么我们应该什么时候更新prediff呢?

我们只需要在 这个坡度 摆动变化的时候,更新prediff就行,这样prediff在 单调区间有平坡的时候 就不会发生变化,造成我们的误判。

所以本题的最终代码为:

class Solution {
public:
    int wiggleMaxLength(vector<int>& nums) {
        if (nums.size() <= 1) return nums.size();
        int curDiff = 0; // 当前一对差值
        int preDiff = 0; // 前一对差值
        int result = 1;  // 记录峰值个数,序列默认序列最右边有一个峰值
        for (int i = 0; i < nums.size() - 1; i++) {
            curDiff = nums[i + 1] - nums[i];
            // 出现峰值
            if ((preDiff <= 0 && curDiff > 0) || (preDiff >= 0 && curDiff < 0)) {
                result++;
                preDiff = curDiff; // 注意这里,只在摆动变化的时候更新prediff 
            }
        }
        return result;
    }
};

53. 最大子序和

文档讲解:代码随想录 (programmercarl.com)

视频讲解:贪心算法的巧妙需要慢慢体会!LeetCode:53. 最大子序和_哔哩哔哩_bilibili

状态:暴力法可以做出来,贪心法做不出来。

思路

**贪心贪的是哪里呢?**如果 -2 1 在一起,计算起点的时候,一定是从1开始计算,因为负数只会拉低总和,这就是贪心贪的地方!

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

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

局部最优的情况下,并记录最大的“连续和”,可以推出全局最优

从代码角度上来讲:遍历nums,从头开始用count累积,如果count一旦加上nums[i]变为负数,那么就应该从nums[i+1]开始从0累积count了,因为已经变为负数的count,只会拖累总和

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

那有同学问了,区间终止位置不用调整么? 如何才能得到最大“连续和”呢?

区间的终止位置,其实就是如果count取到最大值了,及时记录下来了。例如如下代码:

if (count > result) result = count;

这样相当于是用result记录最大子序和区间和(变相的算是调整了终止位置)

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int result = INT32_MIN;
        int count = 0;
        for (int i = 0; i < nums.size(); i++) {
            count += nums[i];
            if (count > result) { // 取区间累计的最大值(相当于不断确定最大子序终止位置)
                result = count;
            }
            if (count <= 0) count = 0; // 相当于重置最大子序起始位置,因为遇到负数一定是拉低总和
        }
        return result;
    }
};

常见误区

那 4 + -1 之后 不就变小了吗? 会不会错过 4 成为最大连续和的可能性?

其实并不会,因为还有一个变量result 一直在更新 最大的连续和,只要有更大的连续和出现,result就更新了,那么result已经把4更新了,后面 连续和变成3,也不会对最后结果有影响。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/490936.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

代码随想录刷题笔记3

文章目录 回溯细节模板题型组合分割子集排列棋盘问题N皇后问题解数独问题 其他 总结 回溯 本质上&#xff1a;穷举 剪枝。回溯法就是解决这种k层for循环嵌套的问题。 for循环横向遍历&#xff0c;递归纵向遍历&#xff0c;回溯不断调整结果集。 注意画出 解空间树-N叉树。 细…

无云服务器,Linux本地快速搭建web网站,并内网穿透发布上线(1)

文章目录 前言1. 本地搭建web站点2. 测试局域网访问3. 公开本地web网站3.1 安装cpolar内网穿透3.2 创建http隧道&#xff0c;指向本地80端口3.3 配置后台服务 4. 配置固定二级子域名5. 测试使用固定二级子域名访问本地web站点 转载自cpolar文章&#xff1a;Linux CentOS本地搭建…

【QT】 Qt高级——Qt自定义标题栏

【QT】 Qt高级——Qt自定义标题栏 一、Qt自定义标题栏简介 QWidget及其子类窗体组件的标题栏受操作系统的控制&#xff0c;即标题栏的界面风格与操作系统的主题风格相同&#xff0c;工程实践中需要开发者自行定义&#xff0c;达到美化应用程序界面的目的。 二、Qt自定义标题…

每日学术速递5.1

CV - 计算机视觉 | ML - 机器学习 | RL - 强化学习 | NLP 自然语言处理 Subjects: cs.CV 1.Putting People in Their Place: Affordance-Aware Human Insertion into Scenes 标题&#xff1a;把人放在他们的位置&#xff1a;可供感知的人类插入场景 作者&#xff1a;Sumit…

项目管理:项目进度跟踪的好处有哪些?

项目进度跟踪主要针对项目计划、任务和项目成员三个方面&#xff0c;即为了了解整个项目计划完成情况、了解项目的实际进展情况、解成员工作完成情况。 项目跟踪可以证明计划是否可执行&#xff0c;可以说明计划是否可以被完成。 在项目执行过程中&#xff0c;我们也可以通过跟…

网络安全合规-Tisax(汽车安全评估讯息交换平台)一

**TISAX&#xff08;汽车安全评估讯息交换平台&#xff08;可信信息安全评估交换平台&#xff09;&#xff09;**是2017年由德国汽车工业联合会(VDA) 联合欧洲网络交换所(ENX) 所推出的资讯交换平台&#xff0c;通过应用欧洲网络交换协会&#xff08;ENX&#xff09;和德国汽车…

rk3568 适配摄像头 (双摄)

rk3568 适配摄像头 (mipi 双摄) rk3568 适配双摄像其实就是逐个适配单摄像头&#xff0c;只是两颗摄像头的数据总线可能不同(cifmipi)&#xff0c;也可能相同(mipi(2lane) x 2)。几乎相同的上电时许&#xff0c;不同的时钟信号和总线协议决定加载过程会略有不同。 提示&#…

CSDN周赛第49期 - 也谈马拉车

前言 C站的周赛已经很久没有新题了&#xff0c;已考过的题目我差不多都写过题解&#xff0c;若再重复写类似的文章&#xff0c;反而会降低博文质量分&#xff0c;而想要换个角度&#xff0c;却又难以找到动笔的欲望。所以虽然比赛发生在五一假期之前&#xff0c;但直到现在五一…

camunda升级事件的用途

在Camunda中&#xff0c;升级事件&#xff08;Escalation Event&#xff09;是一种可以在工作流中出现异常情况时触发相应操作的事件类型。使用升级事件可以帮助工作流更加灵活地处理异常情况&#xff0c;以确保工作流的正常运行。 使用升级事件可以处理以下情况&#xff1a; …

【Java EE 初阶】如何保证线程安全

目录 1.线程是什么&#xff1f; 2.线程安全&#xff08;重点&#xff09; 1.概念&#xff1a; 1.举例&#xff1a;用两个线程分别对同一个变量做五万次自增&#xff0c;观察答案是否符合预期 那么是哪些原因造成了这种线程不安全的现象呢&#xff1f;我们一起来分析一下&am…

搭建Plex媒体服务器,打造家庭多媒体中心【公网远程访问】

文章目录 1.前言2. Plex网站搭建2.1 Plex下载和安装2.2 Plex网页测试2.3 cpolar的安装和注册 3. 本地网页发布3.1 Cpolar云端设置3.2 Cpolar本地设置 4. 公网访问测试5. 结语 1.前言 用手机或者平板电脑看视频&#xff0c;已经算是生活中稀松平常的场景了&#xff0c;特别是各…

MAC安装MySQL

安装MySQL 登录官网dev.mysql.com/downloads/m… 下载社区版mysql&#xff0c;选择dmg格式的安装包。下载完成后&#xff0c;开始安装。 注意&#xff1a;选择Use Legacy Password Encryption。 解决无法启动MySQL问题 打开设置中的mysql图标&#xff0c;发现红点&#xff0…

Syslog-ng RHEL 的安装和配置

syslog-ng 作为 syslog 的替代工具&#xff0c;可以完全替代 syslog 的服务&#xff0c;并且通过定义规则&#xff0c;实现更好的过滤功能。 作为运维来说一个好的日志工具比什么都重要。 通常我们会管理不同的服务器&#xff0c;因此我们需要把日志集中一下以便于快速查找。…

GUI编程(二)

Swing Swing是GUI&#xff08;图形用户界面&#xff09;开发工具包。 早期的AWT&#xff08;抽象窗口工具包&#xff09;组件开发的图形用户界面&#xff0c;要依赖本地系统&#xff0c;当把AWT组件开发的应用程序移植到其他平台的系统上运行时&#xff0c;不能保证其外观风格…

贪心刷题~

1、洛谷P2240 【深基12.例1】部分背包问题 贪心策略&#xff1a;拿金币单价高的。 #include<iostream> #include<cstring> #include<algorithm> using namespace std;struct gold{int v;int m; } q[101];bool cmp(gold a,gold b){return a.v*b.m>b.v*a.m…

SpringCloud-微服务Eureka服务注册中心

微服务&服务注册中心 前言一、微服务1.什么是微服务2.单体架构和微服务架构2.1.单体架构2.2.微服务架构 二、服务注册中心1.服务注册中心简介2.Eureka服务注册中心2.1.Eureka Server开发2.2 Eureka Client开发 3.Eureka的自我保护机制3.1.Eureka自我保护机制简介3.2.Eureka…

MySQL数据库连接超时自动断开的解决方案

这里写自定义目录标题 欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题&#xff0c;有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants 创建一个自定义列表如何创建一个…

windows下msys2编译64位的ffmpeg源码

目前遇到过两次需求&#xff0c;需要编译ffmpeg源码。网上下载的编译好的源码里面可能不全&#xff0c;很多时候需要自行编译源码。本文介绍自行编译ffmpeg64位源码&#xff08;32位通过相似的方式为编译成功&#xff0c;不知道原因&#xff09; 环境&#xff1a; 2023.5.4下载…

团队密码管理器Passbolt的安装

老苏下载了吴恩达联手 OpenAI 推出的 Prompt for developer 课程&#xff0c;总长度大概在一个半小时左右&#xff0c;可以让我们学习正确的 ChatGPT Prompt 工程 虽然课程对话是英文&#xff0c;但有中文字幕&#xff0c;课程地址&#xff1a;https://www.aliyundrive.com/s/…

[Gitops--9]微服务项目sangomall代码配置修改及资源清单文件

微服务项目sangomall代码配置修改及资源清单文件 1. 中间件的地址 1.1 Nacos 集群外 nacos-server.intra.com 192.168.31.211集群内 nacos-server.sangomall.svc.cluster.local. nacos-server.sangomall.svc.cluster.local.:88481.2 Redis 集群内 redis.sangomall.svc.c…