代码随想录Day 27|贪心算法,题目:455.分发饼干、376.摆动序列、53.最大子序和

news2024/11/12 20:49:19

提示:DDU,供自己复习使用。欢迎大家前来讨论~

文章目录

  • 贪心算法Part01
  • 一、理论基础
      • 1.1 什么是贪心
        • 贪心算法解法:
        • 动态规划解法:
      • 1.2 贪心一般解题步骤
  • 二、题目
    • 题目一:455.分发饼干
      • 解题思路:
      • 其他思路
    • 题目二:376.摆动序列
      • 解题思路:
      • 思路二:动态规划
    • 题目三: 53. 最大子序和
      • 解题思路
      • 暴力解法
      • 贪心解法
      • 动态规划
      • 总结


贪心算法Part01

**说白了就是常识性推导加上举反例**

一、理论基础

1.1 什么是贪心

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

贪心算法是“走一步看一步”,而动态规划是“先规划再行动”。

  • 贪心算法适合那些每一步的局部最优选择能够导致全局最优解的问题;
  • 动态规划适合那些问题可以分解成重叠子问题,且可以通过解决这些子问题来构建原问题解的情况。

经典问题:背包问题

假设你是一个小偷,偷到了一个背包,背包的容量有限(比如10公斤)。你面前有若干件物品,每件物品都有自己的重量和价值。你希望带走尽可能多的价值,但同时不能超过背包的重量限制。

贪心算法解法:
  1. 排序:首先,你把所有物品按照价值和重量的比例(价值/重量)从高到低排序。
  2. 选择:然后,你从最上面开始,依次尝试把物品放入背包,直到背包装满或者没有更多的物品。

这种方法简单快速,但并不保证你带走的是价值最高的组合。因为你只考虑了当前价值最高的物品,而没有考虑其他物品组合的可能性。

动态规划解法:
  1. 创建表格:你创建一个表格,表格的行代表物品,列代表背包的容量(从0到10公斤)。
  2. 填表:对于每个物品和每个容量,你计算如果选择这个物品(如果不超过当前容量)和不选择这个物品(即选择前一个容量的最大价值)的最大价值。
  3. 选择:通过表格,你可以找到在不超过背包容量的情况下,能够带走的最大价值的组合。

这种方法需要更多的计算,但它能够保证你找到的是价值最高的组合,因为它考虑了所有可能的物品组合。

  • 贪心算法:快速但不一定最优,适用于那些每一步选择局部最优能够导致全局最优的问题。
  • 动态规划:虽然计算量大,但能够保证找到最优解,适用于那些可以分解为重叠子问题的问题。

1.2 贪心一般解题步骤

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

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

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

二、题目

题目一:455.分发饼干

455. 分发饼干

解题思路:

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

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

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

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

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

img

代码如下:

// 版本一
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;
    }
};
  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(1)

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

有的同学看到要遍历两个数组,就想到用两个 for 循环,那样逻辑其实就复杂了。

其他思路

也可以换一个思路,小饼干先喂饱小胃口

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;
    }
};
  • 时间复杂度:O(nlogn)
  • 空间复杂度:O(1)

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

题目二:376.摆动序列

376. 摆动序列

解题思路:

376.摆动序列

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

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

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

**实际操作上,其实连删除的操作都不用做,因为题目要求的是最长摆动子序列的长度,所以只需要统计数组的峰值数量就可以了(相当于是删除单一坡度上的节点,然后统计长度)**这就是贪心所贪的地方,让峰值尽可能的保持峰值,然后删除单一坡度上的节点

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

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

  1. 情况一:上下坡中有平坡
  2. 情况二:数组首尾两端
  3. 情况三:单调坡中有平坡
// 版本二
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;
    }
};

思路二:动态规划

考虑用动态规划的思想来解决这个问题。

很容易可以发现,对于我们当前考虑的这个数,要么是作为山峰(即 nums[i] > nums[i-1]),要么是作为山谷(即 nums[i] < nums[i - 1])。

  • 设 dp 状态dp[i][0],表示考虑前 i 个数,第 i 个数作为山峰的摆动子序列的最长长度
  • 设 dp 状态dp[i][1],表示考虑前 i 个数,第 i 个数作为山谷的摆动子序列的最长长度

则转移方程为:

  • dp[i][0] = max(dp[i][0], dp[j][1] + 1),其中0 < j < inums[j] < nums[i],表示将 nums[i]接到前面某个山谷后面,作为山峰。
  • dp[i][1] = max(dp[i][1], dp[j][0] + 1),其中0 < j < inums[j] > nums[i],表示将 nums[i]接到前面某个山峰后面,作为山谷。

初始状态:

由于一个数可以接到前面的某个数后面,也可以以自身为子序列的起点,所以初始状态为:dp[0][0] = dp[0][1] = 1

C++代码如下:

class Solution {
public:
    int dp[1005][2];
    int wiggleMaxLength(vector<int>& nums) {
        memset(dp, 0, sizeof dp);
        dp[0][0] = dp[0][1] = 1;
        for (int i = 1; i < nums.size(); ++i) {
            dp[i][0] = dp[i][1] = 1;
            for (int j = 0; j < i; ++j) {
                if (nums[j] > nums[i]) dp[i][1] = max(dp[i][1], dp[j][0] + 1);
            }
            for (int j = 0; j < i; ++j) {
                if (nums[j] < nums[i]) dp[i][0] = max(dp[i][0], dp[j][1] + 1);
            }
        }
        return max(dp[nums.size() - 1][0], dp[nums.size() - 1][1]);
    }
};
  • 时间复杂度:O(n^2)
  • 空间复杂度:O(n)

进阶

可以用两棵线段树来维护区间的最大值

  • 每次更新dp[i][0],则在tree1nums[i]位置值更新为dp[i][0]
  • 每次更新dp[i][1],则在tree2nums[i]位置值更新为dp[i][1]
  • 则 dp 转移方程中就没有必要 j 从 0 遍历到 i-1,可以直接在线段树中查询指定区间的值即可。

时间复杂度:O(nlog n)

空间复杂度:O(n)

题目三: 53. 最大子序和

53. 最大子数组和

解题思路

暴力解法

暴力解法的思路,第一层 for 就是设置起始位置,第二层 for 循环遍历数组寻找最大值

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        int result = INT32_MIN;
        int count = 0;
        for (int i = 0; i < nums.size(); i++) { // 设置起始位置
            count = 0;
            for (int j = i; j < nums.size(); j++) { // 每次从起始位置i开始遍历寻找最大值
                count += nums[j];
                result = count > result ? count : result;
            }
        }
        return result;
    }
};
  • 时间复杂度:O(n^2)
  • 空间复杂度:O(1)

贪心解法

  1. 贪心算法的核心:贪心算法在每一步都选择当前看起来最优的选项,希望这样能够达到全局最优。例子中,“贪”的是当前的正数,以期望累积更大的和。
  2. 局部最优:在贪心算法中,如果当前元素是正数,就将其加入到当前的子数组中,因为正数会增加当前的连续和。如果当前元素是负数,就从下一个元素开始新的子数组,因为负数会减少当前的连续和。
  3. 全局最优:虽然贪心算法在每一步都做出了局部最优的选择,但并不保证能够得到全局最优解。例子中,贪心算法可能无法找到整个数组中的最大连续正和。
  4. 代码实现:在代码层面,使用一个变量count来累积当前的连续和。当count加上当前元素nums[i]变为负数时,就将count重置为0,从nums[i+1]开始新的累积。
  5. 区间终止位置:贪心算法在这个问题中不调整区间的终止位置,因为它总是贪心地选择当前的正数,直到遇到下一个负数。这样可能无法找到真正的最大子数组和。
  6. 动态规划:与贪心算法不同,动态规划会考虑所有可能的子数组,通过填表的方式来记录每个子数组的和,并选择最大的那个。这种方法可以保证找到全局最优解。
  7. 暴力解法:暴力解法会尝试所有可能的子数组组合,计算它们的和,然后选择最大的一个。这种方法虽然能够找到全局最优解,但效率较低。

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

if (count > result) result = count;

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

如动画所示:

53.最大子序和

红色的起始位置就是贪心每次取 count 为正数的时候,开始一个区间的统计。

那么不难写出如下 C++代码(关键地方已经注释)

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;
    }
};
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

当然题目没有说如果数组为空,应该返回什么,所以数组为空的话返回啥都可以了。

动态规划

class Solution {
public:
    int maxSubArray(vector<int>& nums) {
        if (nums.size() == 0) return 0;
        vector<int> dp(nums.size(), 0); // dp[i]表示包括i之前的最大连续子序列和
        dp[0] = nums[0];
        int result = dp[0];
        for (int i = 1; i < nums.size(); i++) {
            dp[i] = max(dp[i - 1] + nums[i], nums[i]); // 状态转移公式
            if (dp[i] > result) result = dp[i]; // result 保存dp[i]的最大值
        }
        return result;
    }
};
  • 时间复杂度:O(n)
  • 空间复杂度:O(n)

小结:

本题的贪心思路其实并不好想,这也进一步验证了,别看贪心理论很直白,有时候看似是常识,但贪心的题目一点都不简单!

总结

  • 贪心算法的基本理论,局部最优但不一定是全局最优。
  • 贪心算法的组合问题,经典的背包问题

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

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

相关文章

【Datawhale AI夏令营第五期】 CV方向 Task02学习笔记 精读Baseline 建模方案解读与进阶

【Datawhale AI夏令营第五期】 CV方向 Task02学习笔记 精读Baseline 建模方案解读与进阶 教程&#xff1a; 链接&#xff1a; https://linklearner.com/activity/16/16/68 传送门 之前我看原画课的时候&#xff0c;造型的部分就跟我们说&#xff0c;让我们日常观察事物的时候…

海运系统:海运拼箱 小批量货物的海运奥秘

在国际海运运输的广阔领域中&#xff0c;海运拼箱作为一种灵活且经济的运输方式&#xff0c;尤其适用于那些货物量不大或体积不足以单独填满一个标准集装箱的场景。这种运输模式不仅促进了国际贸易的便捷性&#xff0c;还通过资源共享的方式&#xff0c;有效降低了物流成本&…

p10 容器的基本命令

首先先拉取一个centos的镜像 命令&#xff1a;docker pull centos 新建容器并且启动 这里直接参考老师的命令吧 接下来是启动并且进入到容器当中去输入docker run -it centos /bin/bash这里是以交互的方式进入到容器中可以看到接下来的ls命令输出的东西就是一个Linux系统最…

Ansys Speos | 挡风玻璃光学畸变分析

附件下载 联系工作人员获取附件 此示例介绍了基于 TL 957 标准和43号法规&#xff08;ECE R43&#xff09;的挡风玻璃光学畸变分析的工作流程&#xff0c;以及 GitHub Ansys 光学自动化中提供的分析自动化工具。 如果您从未使用过任何 GitHub 仓库&#xff0c;可以根据光学自…

数据结构(邓俊辉)学习笔记】串 07——KMP算法:分摊分析

文章目录 1.失之粗糙2.精准估计 1.失之粗糙 以下&#xff0c;就来对 KMP 算法的性能做一分析。我们知道 KMP 算法的计算过程可以根据对齐位置相应的分为若干个阶段&#xff0c;然而每一个阶段所对应的计算量是有很大区别的。很快就会看到&#xff0c;如果只是简单地从最坏的角…

K8S的持久化存储

文章目录 一、持久化存储emptyDir实际操作 hostPath建立过程 NFS存储NFS 存储的优点NFS 存储的缺点具体操作 pv和pvcPersistent Volume (PV)使用场景 Persistent Volume Claim (PVC)使用场景 使用 PV 和 PVC 的场景实际操作 StorageClassStorageClass 概述应用场景实际应用 一、…

实用攻略:亲身试用,高效数据恢复软件推荐!

今天要跟大家分享一下我使用几款数据恢复软件的经历。如果你曾经丢失过重要的文件&#xff0c;那除了注意备份外&#xff0c;也可以尝试一下这些非常棒的免费数据恢复软件&#xff01; 第一款&#xff1a;福昕数据恢复 链接&#xff1a;www.pdf365.cn/foxit-restore/ 首先聊…

Nginx+ModSecurity(3.0.x)安装教程及配置WAF规则文件

本文主要介绍ModSecurity v3.0.x在Nginx环境下的安装、WAF规则文件配置、以及防御效果的验证&#xff0c;因此对于Nginx仅进行简单化安装。 服务器操作系统&#xff1a;linux 位最小化安装 一、安装相关依赖工具 Bash yum install -y git wget epel-release yum install -y g…

大模型企业应用落地系列二》基于大模型的对话式推荐系统》核心技术架构设计图

注&#xff1a;此文章内容均节选自充电了么创始人&#xff0c;CEO兼CTO陈敬雷老师的新书《自然语言处理原理与实战》&#xff08;人工智能科学与技术丛书&#xff09;【陈敬雷编著】【清华大学出版社】 文章目录 大模型企业应用落地系列二基于大模型的对话式推荐系统》心技术架…

【精品】计算机毕业设计之:springboot游戏分享网站(源码+文档+辅导)

博主介绍&#xff1a; ✌我是阿龙&#xff0c;一名专注于Java技术领域的程序员&#xff0c;全网拥有10W粉丝。作为CSDN特邀作者、博客专家、新星计划导师&#xff0c;我在计算机毕业设计开发方面积累了丰富的经验。同时&#xff0c;我也是掘金、华为云、阿里云、InfoQ等平台…

win11,vscode上用docker环境跑项目

1.首先用dockerfile创建docker镜像 以下是dockerfile文件的内容&#xff1a; FROM pytorch/pytorch:1.11.0-cuda11.3-cudnn8-devel LABEL Service"SparseInstanceActivation"ENV TZEurope/Moscow ENV DETECTRON_TAGv0.6 ARG DEBIAN_FRONTENDnoninteractiveRUN apt-…

JavaScript:js;知识回顾;笔记分享

一&#xff0c;js前奏 1&#xff0c;js简介&#xff1a; Javascript是一种由Netscape(网景)的LiveScript发展而来的原型化继承的面向对象的动态类型的区分大小写的客户端脚本语言&#xff0c;主要目的是为了解决服务器端语言&#xff0c;比如Perl&#xff0c;遗留的速度问题&a…

数据结构与算法(1)

抽象数据类型定义 算法的效率 时间效率 一个算法的运行时间是指一个算法在计算机上运行所耗费的时间 大致可以等于计算机执行一种简单的操作(如赋值、比较、移动等) 所需的时间与算法中进行的简单操作次数乘积。 比较时间复杂度&#xff08;看数量级&#xff09; 空…

【STM32】RTC

忠实的时间记录者——RTC&#xff0c;板子断电也能继续记录&#xff08;当然&#xff0c;得有纽扣电池供电&#xff09;。 大部分图片来源&#xff1a;正点原子HAL库课程 专栏目录&#xff1a;记录自己的嵌入式学习之路-CSDN博客 目录 1 概述 2 应用 3 RTC方案 4 …

javacv-ffmpeg ProcessBuilder批量旋转图片

javacv-ffmpeg ProcessBuilder实现对图片的旋转&#xff0c;最近需要处理很多图片&#xff0c;量有点多&#xff0c;所以不能一个一个去编辑旋转图片&#xff0c;所以写一个工具类&#xff0c;实现对图片的旋转 maven配置文件&#xff0c;加上对ffmpeg的依赖&#xff0c;由于f…

关于自定义控件,头文件找不到问题的解决办法

在ui文件中使用控件提升的时候&#xff0c;突然发现ui_xxxx.h竟然报错了&#xff0c; 这在之前是没有碰到过&#xff0c;苦思过后认为是环境变量的问题&#xff0c;因为现在正在用的构建套件是 mingw 的&#xff0c;但在此之前我一直用的是msvc&#xff0c;并且环境变量的配置…

骑行耳机该怎么选?精选南卡、骨聆、小米多款顶尖实用品牌!

骨传导耳机&#xff0c;作为运动骑行领域的黑科技伴侣&#xff0c;以其独特的传音方式&#xff0c;让众多骑行运动爱好者在享受音乐的同时&#xff0c;依然能清晰感知外界环境&#xff0c;提升运动安全与乐趣。然而&#xff0c;随着骨传导耳机热度增加&#xff0c;市面上开始出…

Prometheus监控Kubernetes ETCD

文章目录 一、kubeadm方式部署etcd1.修改etcd指标接口监听地址2.prometheus中添加etcd的服务发现配置3.创建etcd的service4.grafana添加etcd监控模版 二、二进制方式部署k8s etcd1.将etcd服务代理到k8s集群2.创建etcd证书的secrets3.prometheus挂载etcd证书的secrets4.promethe…

63. 不同路径 II -dp7

63. 不同路径 IIhttps://leetcode.cn/problems/unique-paths-ii/ 输入&#xff1a;obstacleGrid [[0,0,0],[0,1,0],[0,0,0]] 输出&#xff1a;2 解释&#xff1a;3x3 网格的正中间有一个障碍物。 从左上角到右下角一共有 2 条不同的路径&#xff1a; 1. 向右 -> 向右 ->…

Chrome 中的 RCE 会在 JIT 编译器中产生不正确的副作用

此类漏洞通常是“一键式”攻击的起点,当受害者访问恶意网站时,该漏洞会危害受害者的设备。Chrome 中的渲染器 RCE 允许攻击者危害 Chrome 渲染器进程并执行任意代码。但是,渲染器进程的权限有限,因此攻击者需要将此类漏洞与第二个“沙盒逃逸”漏洞串联起来:Chrome 浏览器进…