一刷代码随想录(贪心12)

news2024/11/16 15:33:48

贪心算法理论基础

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

例如刚刚举的拿钞票的例子,就是模拟一下每次拿做大的,最后就能拿到最多的钱,这还要数学证明的话,其实就不在算法面试的范围内了,可以看看专业的数学书籍!

所以这也是为什么很多同学通过(accept)了贪心的题目,但都不知道自己用了贪心算法,因为贪心有时候就是常识性的推导,所以会认为本应该就这么做!

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

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

分配饼干

题意:

假设你是一位很棒的家长,想要给你的孩子们一些小饼干。但是,每个孩子最多只能给一块饼干。

对每个孩子 i,都有一个胃口值  g[i],这是能让孩子们满足胃口的饼干的最小尺寸;并且每块饼干 j,都有一个尺寸 s[j] 。如果 s[j] >= g[i],我们可以将这个饼干 j 分配给孩子 i ,这个孩子会得到满足。你的目标是尽可能满足越多数量的孩子,并输出这个最大数值。

示例  1:

输入: g = [1,2,3], s = [1,1]
输出: 1 解释:你有三个孩子和两块小饼干,3 个孩子的胃口值分别是:1,2,3。虽然你有两块小饼干,由于他们的尺寸都是 1,你只能让胃口值是 1 的孩子满足。所以你应该输出 1。
示例  2:

输入: g = [1,2], s = [1,2,3]
输出: 2
解释:你有两个孩子和三块小饼干,2 个孩子的胃口值分别是 1,2。你拥有的饼干数量和尺寸都足以让所有孩子满足。所以你应该输出 2.

解法:

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

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

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

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

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

代码:

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

 摆动序列

题意:

如果连续数字之间的差严格地在正数和负数之间交替,则数字序列称为摆动序列。第一个差(如果存在的话)可能是正数或负数。少于两个元素的序列也是摆动序列。

例如, [1,7,4,9,2,5] 是一个摆动序列,因为差值 (6,-3,5,-7,3)  是正负交替出现的。相反, [1,4,7,2,5]  和  [1,7,4,5,5] 不是摆动序列,第一个序列是因为它的前两个差值都是正数,第二个序列是因为它的最后一个差值为零。

给定一个整数序列,返回作为摆动序列的最长子序列的长度。 通过从原始序列中删除一些(也可以不删除)元素来获得子序列,剩下的元素保持其原始顺序。

示例 1:

输入: [1,7,4,9,2,5]
输出: 6
解释: 整个序列均为摆动序列。
示例 2:

输入: [1,17,5,10,13,15,10,5,16,8]
输出: 7
解释: 这个序列包含几个长度为 7 摆动序列,其中一个可为[1,17,10,13,10,16,8]。
示例 3:

输入: [1,2,3,4,5,6,7,8,9]
输出: 2
解法:

直接记录峰值个数即可,使用两个值来记录前后该点与前后差距,若前后差距符号不同则记录,若遇到平坡,可以记录左右节点中的一个,也就是前项差值为零,后项差值不为零的位置,此时就引入问题,即再单调过程中出现平坡该如何处理,如果出现平坡就会导致,进入平坡的点被记入,以及出平坡继续向上的点被记入,如果要避免该情况,则要在摆动的位置进行更新之前的pre,由于之前的cur被记入,只有当cur为反向时才有记录的意义。

代码:

// 版本二
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 ,找到一个具有最大和的连续子数组(子数组最少包含一个元素),返回其最大和。

示例:

  • 输入: [-2,1,-3,4,-1,2,1,-5,4]
  • 输出: 6
  • 解释:  连续子数组  [4,-1,2,1] 的和最大,为  6。

解法:贪心算法,若在某一位置,连续和是负数,那么往后叠加必然会少于下一个点的值,那不如从下一个点开始直接舍弃原来的和,从下一位置开始计数。

代码:

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

买卖股票的最佳时机

题意:

给定一个数组,它的第  i 个元素是一支给定股票第 i 天的价格。

设计一个算法来计算你所能获取的最大利润。你可以尽可能地完成更多的交易(多次买卖一支股票)。

注意:你不能同时参与多笔交易(你必须在再次购买前出售掉之前的股票)。

示例 1:

  • 输入: [7,1,5,3,6,4]
  • 输出: 7
  • 解释: 在第 2 天(股票价格 = 1)的时候买入,在第 3 天(股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4。随后,在第 4 天(股票价格 = 3)的时候买入,在第 5 天(股票价格 = 6)的时候卖出, 这笔交易所能获得利润 = 6-3 = 3 。

示例 2:

  • 输入: [1,2,3,4,5]
  • 输出: 4
  • 解释: 在第 1 天(股票价格 = 1)的时候买入,在第 5 天 (股票价格 = 5)的时候卖出, 这笔交易所能获得利润 = 5-1 = 4 。注意你不能在第 1 天和第 2 天接连购买股票,之后再将它们卖出。因为这样属于同时参与了多笔交易,你必须在再次购买前出售掉之前的股票。

示例  3:

  • 输入: [7,6,4,3,1]
  • 输出: 0
  • 解释: 在这种情况下, 没有交易完成, 所以最大利润为 0。

提示:

  • 1 <= prices.length <= 3 * 10 ^ 4
  • 0 <= prices[i] <= 10 ^ 4

解法:由于交易单元是以两天为单位,所以可以计算出每两天的利润,只找正利润累加,即可

代码:

class Solution {
public:
    int maxProfit(vector<int>& prices) {
        int result = 0;
        for (int i = 1; i < prices.size(); i++) {
            result += max(prices[i] - prices[i - 1], 0);
        }
        return result;
    }
};

跳跃问题

题意:

给定一个非负整数数组,你最初位于数组的第一个位置。

数组中的每个元素代表你在该位置可以跳跃的最大长度。

判断你是否能够到达最后一个位置。

示例  1:

  • 输入: [2,3,1,1,4]
  • 输出: true
  • 解释: 我们可以先跳 1 步,从位置 0 到达 位置 1, 然后再从位置 1 跳 3 步到达最后一个位置。

示例  2:

  • 输入: [3,2,1,0,4]
  • 输出: false
  • 解释: 无论怎样,你总会到达索引为 3 的位置。但该位置的最大跳跃长度是 0 , 所以你永远不可能到达最后一个位置。

解法:每一格子都跳最大,找出每一个能覆盖的最大位置,并以其为遍历终点,并实时更新覆盖的最大位置,若覆盖最终覆盖的最大位置大于等于数组索引最大值即可。

代码:

class Solution {
public:
    bool canJump(vector<int>& nums) {
        int cover = 0;
        if (nums.size() == 1) return true; // 只有一个元素,就是能达到
        for (int i = 0; i <= cover; i++) { // 注意这里是小于等于cover
            cover = max(i + nums[i], cover);
            if (cover >= nums.size() - 1) return true; // 说明可以覆盖到终点了
        }
        return false;
    }
};

跳跃问题

题意:

给定一个非负整数数组,你最初位于数组的第一个位置。

数组中的每个元素代表你在该位置可以跳跃的最大长度。

你的目标是使用最少的跳跃次数到达数组的最后一个位置。

示例:

  • 输入: [2,3,1,1,4]
  • 输出: 2
  • 解释: 跳到最后一个位置的最小跳跃数是 2。从下标为 0 跳到下标为 1 的位置,跳  1  步,然后跳  3  步到达数组的最后一个位置。

说明: 假设你总是可以到达数组的最后一个位置。

解法:贪心,每次都走最多步,就能减少跳跃次数,先走到第一次能覆盖的最远,其中不断遍历更新最远距离,走到覆盖的最远距离后,若没有达到终点步数+1,并用当前最新的范围当做下一次走的最远距离,若在其中遇到或是最后直接返回步数。

代码:

class Solution {
public:
    int jump(vector<int>& nums) {
        if (nums.size() == 1) return 0;
        int curDistance = 0;    // 当前覆盖最远距离下标
        int ans = 0;            // 记录走的最大步数
        int nextDistance = 0;   // 下一步覆盖最远距离下标
        for (int i = 0; i < nums.size(); i++) {
            nextDistance = max(nums[i] + i, nextDistance);  // 更新下一步覆盖最远距离下标
            if (i == curDistance) {                         // 遇到当前覆盖最远距离下标
                ans++;                                  // 需要走下一步
                curDistance = nextDistance;             // 更新当前覆盖最远距离下标(相当于加油了)
                if (nextDistance >= nums.size() - 1) break;  // 当前覆盖最远距到达集合终点,不用做ans++操作了,直接结束
            }
        }
        return ans;
    }
};

也可以以规定只能遍历到倒数第二个数,按照正常更新最大范围,若到第二位还没走到最大范围,步数不加,反之步数再加一。

代码:

// 版本二
class Solution {
public:
    int jump(vector<int>& nums) {
        int curDistance = 0;    // 当前覆盖的最远距离下标
        int ans = 0;            // 记录走的最大步数
        int nextDistance = 0;   // 下一步覆盖的最远距离下标
        for (int i = 0; i < nums.size() - 1; i++) { // 注意这里是小于nums.size() - 1,这是关键所在
            nextDistance = max(nums[i] + i, nextDistance); // 更新下一步覆盖的最远距离下标
            if (i == curDistance) {                 // 遇到当前覆盖的最远距离下标
                curDistance = nextDistance;         // 更新当前覆盖的最远距离下标
                ans++;
            }
        }
        return ans;
    }
};

K次反转后取最大的数组和

题意:

给定一个整数数组 A,我们只能用以下方法修改该数组:我们选择某个索引 i 并将 A[i] 替换为 -A[i],然后总共重复这个过程 K 次。(我们可以多次选择同一个索引 i。)

以这种方式修改数组后,返回数组可能的最大和。

示例 1:

  • 输入:A = [4,2,3], K = 1
  • 输出:5
  • 解释:选择索引 (1) ,然后 A 变为 [4,-2,3]。

示例 2:

  • 输入:A = [3,-1,0,2], K = 3
  • 输出:6
  • 解释:选择索引 (1, 2, 2) ,然后 A 变为 [3,1,0,2]。

示例 3:

  • 输入:A = [2,-3,-1,5,-4], K = 2
  • 输出:13
  • 解释:选择索引 (1, 4) ,然后 A 变为 [2,3,-1,5,4]。

提示:

  • 1 <= A.length <= 10000
  • 1 <= K <= 10000
  • -100 <= A[i] <= 100
  • 解法:将绝对值大的负数先反转,负数全反转完发现,k不为零,则反复反转绝对值最小的数,直到k=0,需要将数字按照绝对值分类。

代码:

class Solution {
static bool cmp(int a, int b) {
    return abs(a) > abs(b);
}
public:
    int largestSumAfterKNegations(vector<int>& A, int K) {
        sort(A.begin(), A.end(), cmp);       // 第一步
        for (int i = 0; i < A.size(); i++) { // 第二步
            if (A[i] < 0 && K > 0) {
                A[i] *= -1;
                K--;
            }
        }
        if (K % 2 == 1) A[A.size() - 1] *= -1; // 第三步
        int result = 0;
        for (int a : A) result += a;        // 第四步
        return result;
    }
};

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

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

相关文章

对称字符串

import java.util.Scanner; /*** author gyf* ClassName Test* Date 2024/7/31 13:39* Version V1.0* Description :*/ public class Test {public static void main(String[] args) {// StringBuilder 的场景// 1.字符串的拼接// 2.字符串的反转Scanner scanner new Scanner(…

Maven问题:IDEA无法创建javaweb和没有web Application支持的方案

解决方案一&#xff1a; 项目右键单击&#xff0c;点击add framework support 完成后就出现了web文件夹 解决方案二 右键单击后没有add framework support,先制作该功能的快捷键&#xff0c;添加快捷键之后按照解决方案1操作 注&#xff1a; 如果按照以上步骤均创建不成…

springboot业务层service开发全过程(以mybatis-plus为例)

在配置完数据层Dao/Mapper层的基础上&#xff0c;接下来我们要开始实现业务层的开发。 数据层和业务层的区别&#xff1a; 简单来说业务层是数据层的一个升级&#xff0c;从名字上也可以看出&#xff0c;数据层要想查询一个ID&#xff0c;都是需要定义SelectById这样的名称&a…

零门槛AI 绘图:教你为客户定制 ComfyUI Serverless API 应用

作者&#xff1a;鸥弋、筱姜 2023年下半年&#xff0c;ComfyUI 以其快速、流畅的图像生成能力&#xff0c;结合多样的自定义节点&#xff0c;迅速在创作者中流行起来。ComfyUI 的亮点就是能够批量化生成图像&#xff0c;一键加载大量工作流&#xff0c;让用户可以轻松实现人像…

代理仓业务好做吗?有没有什么系统可以打理的?

做海外仓代理相对自建海外仓而言&#xff0c;风险较小&#xff0c;更适合货代&#xff0c;但也需要面对一些难题&#xff1a;仓库管理、市场拓展、多仓协同、打通接口等。易境通DWMS系统就可以让海外仓代理变得简单明晰&#xff0c;让刚进入这个领域的小白也可以成功转型&#…

各类网页设计的设计尺寸大揭秘

我们讨论网页的大小时&#xff0c;我们需要明白这不仅仅是一个数字。在一个充满随时变化和创新的数字世界中&#xff0c;网页的建立和设计应考虑设备类型、屏幕大小和分辨率、用户浏览习惯等诸多因素。本文将深入分析这个主题&#xff0c;以帮助您充分理解这种情况&#xff0c;…

tensorRT 自带测试样例 sampleOnnxMNIST 源码分析

该测试样例是进行手写数字识别&#xff0c;使用 Visual Studio 2022 打开 tensorRT安装目录下的 samples 下的 sampleOnnxMNIST 工程( 例如我的是 D:\install\tensorRT\TensorRT-8.6.1.6\samples\sampleOnnxMNIST )&#xff0c;执行输出结果&#xff1a; 成功识别出数字0&#…

ai模特换装软件哪个好用?不知道怎么穿搭就用这几个

最近#紫色跑道的city穿搭#风靡全网&#xff0c;大家纷纷晒出自己的紫色风情。 可一想到衣橱里堆积如山的衣服和钱包的“瘦身计划”&#xff0c;是不是有点小纠结&#xff1f; 别怕&#xff0c;科技来救场&#xff01;那就是“一键换装在线工具”&#xff0c;让你无需剁手&…

【初阶数据结构】11.排序(2)

文章目录 2.3 交换排序2.3.1 冒泡排序2.3.2 快速排序2.3.2.1 hoare版本2.3.2.2 挖坑法2.3.2.3 lomuto前后指针2.3.2.4 非递归版本 2.4 归并排序2.5 测试代码&#xff1a;排序性能对比2.6 非比较排序2.6.1 计数排序 3.排序算法复杂度及稳定性分析 2.3 交换排序 交换排序基本思想…

2024最新 Navicat Premium 17 简体中文激活版详细安装教程(最简单的激活方式)

一、下载地址 下载链接&#xff1a;分享文件&#xff1a;Navicat Premium 17.0.8 (x64) 中文版.zip 二、安装步骤 1、解压后点击运行navicat170_premium_cs_x64.exe 2、开始安装 3、选择安装路径&#xff0c;最好不要放在系统盘C盘&#xff0c;后面两个步骤默认 4、安装中&a…

【小知识】黑白分明的计算机世界——关系表达式,逻辑表达式和三目运算符

【小知识】黑白分明的计算机世界——关系表达式&#xff0c;逻辑表达式和三目运算符 1.逻辑变量2.关系表达式和逻辑表达式2.1.关系表达式2.1.1.例题——a和b的关系2.1.2.浮点数精度误差 2.2.逻辑表达式2.2.1.常见的逻辑运算符2.2.2.优先级2.2.3.注意事项2.2.3.1.在写逻辑表达式…

书生大模型学习笔记 - 连接云端开发机

申请InternStudio开发机&#xff1a; 这里进去报名参加实战营即可获取 书生大模型实战营 InternStudio平台 创建开发机 SSH连接开发机&#xff1a; SSH免密码登录 本地创建SSH密钥 ssh-keygen -t rsa打开以下文件获取公钥 ~/.ssh/id_rsa.pub去InternStudio添加公钥 …

OPenCV高级编程——OpenCV常见的API及绘图知识详解

目录 引言 一、Mat类详解 1. Mat类的基本结构 2. Mat类的数据类型 3. Mat类的创建与初始化 4. Mat类的使用技巧 二、OpenCV核心功能模块 1. 基本的图像读取与显示 2. 图像的保存 3. 矩阵操作 4. 等待键盘输入与销毁窗口 5. 命名窗口 三、图像处理模块 1. 色彩空间…

一个简单的车辆目标检测和跟踪示例

点击下方卡片&#xff0c;关注“小白玩转Python”公众号 介绍 目标检测&#xff1a;目标检测是指在图像或视频帧中识别和定位特定目标&#xff0c;并使用边界框来确定它们的位置。YOLO&#xff08;You Only Look Once&#xff09;是一种高效的单阶段目标检测算法&#xff0c;以…

普冉Puya 超高性价比M0 MCU 工业电子解决方案

普冉半导体(上海)股份有限公司成立于2016年&#xff0c;总部位于上海张江高科&#xff0c;公司目前主要产品包括微控制器芯片、非易失性存储器芯片及模拟产品。产品广泛应用于物联网、智能手机及周边、可穿戴、服务器、光模块、工业控制、汽车电子、安防等领域。公司在深圳、韩…

Spring Boot集成udp通讯

Spring Boot集成udp通讯 加入依赖编辑配置文件配置相关属性具体业务类客户端调试 加入依赖 <!--加入UDP通信所需依赖--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-integration</artifactId&…

GD32 MCU电源复位和系统复位有什么区别

GD32 MCU的复位分为电源复位和系统复位&#xff0c;电源复位又称为冷复位&#xff0c;相较于系统复位&#xff0c;上电复位更彻底&#xff0c;下面为大家详细介绍上电复位和系统复位的实现以及区别。 电源复位包括上电/掉电复位或者从standby模式唤醒产生的复位&#xff0c;电…

HarmonyOS NEXT——奇妙的调用方式

注解调用一句话总结Extend抽取特定组件样式、事件&#xff0c;可以传递参数Style抽取公共样式、事件&#xff0c;不可以传递参数Builder抽取结构、样式、事件&#xff0c;可以传递参数BuilderParams自定义组件中传递UI组件多个BuilderParams自定义组件中传递多个UI组件 Extend…

echarts加载区域地图,并标注点

效果如下&#xff0c;加载了南海区域的地图&#xff0c;并标注几个气象站点&#xff1b; 1、下载区域地图的JSON&#xff1a;DataV.GeoAtlas地理小工具系列 新建nanhai.json&#xff0c;把下载的JSON数据放进来 说明&#xff1a;如果第二步不打勾&#xff0c;只显示省的名字&a…

全新微软语音合成网页版源码,短视频影视解说配音网页版系统-仿真人语音

源码介绍 最新微软语音合成网页版源码&#xff0c;可以用来给影视解说和短视频配音。它是TTS文本转语言&#xff0c;API接口和PHP源码。 这个微软语音合成接口的源码&#xff0c;超级简单&#xff0c;就几个文件搞定。用的是官方的API&#xff0c;试过了&#xff0c;合成速度…