代码随想录Day 29|leetcode题目:134.加油站、135.分发糖果、860.柠檬水找零、406.根据身高重建队列

news2024/9/21 4:30:55

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

文章目录

  • 第八章 贪心算法 part03
  • 二、题目
    • 题目一:134. 加油站
      • 解题思路:
      • 暴力方法
      • 贪心算法(方法一)
      • 贪心算法(方法二)
    • 题目二:135. 分发糖果
      • 解题思路:
    • 题目三:860.柠檬水找零
      • 解题思路:
    • 题目四:406.根据身高重建队列
      • 解题思路
  • 总结


第八章 贪心算法 part03

贪心算法

二、题目

题目一:134. 加油站

134. 加油站

解题思路:

暴力方法

暴力的方法很明显就是O(n^2)的,遍历每一个加油站为起点的情况,模拟一圈。

如果跑了一圈,中途没有断油,而且最后油量大于等于0,说明这个起点是ok的。

暴力的方法思路比较简单,但代码写起来也不是很容易,关键是要模拟跑一圈的过程。

for循环适合模拟从头到尾的遍历,而while循环适合模拟环形遍历,要善于使用while!

C++代码如下:

class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        for (int i = 0; i < cost.size(); i++) {
            int rest = gas[i] - cost[i]; // 记录剩余油量
            int index = (i + 1) % cost.size();
            while (rest > 0 && index != i) { // 模拟以i为起点行驶一圈(如果有rest==0,那么答案就不唯一了)
                rest += gas[index] - cost[index];
                index = (index + 1) % cost.size();
            }
            // 如果以i为起点跑一圈,剩余油量>=0,返回该起始位置
            if (rest >= 0 && index == i) return i;
        }
        return -1;
    }
};
  • 时间复杂度:O(n^2)
  • 空间复杂度:O(1)

贪心算法(方法一)

直接从全局进行贪心选择,情况如下:

  • 情况一:如果gas的总和小于cost总和,那么无论从哪里出发,一定是跑不了一圈的
  • 情况二:rest[i] = gas[i]-cost[i]为一天剩下的油,i从0开始计算累加到最后一站,如果累加没有出现负数,说明从0出发,油就没有断过,那么0就是起点。
  • 情况三:如果累加的最小值是负数,汽车就要从非0节点出发,从后向前,看哪个节点能把这个负数填平,能把这个负数填平的节点就是出发节点。

C++代码如下:

class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        int curSum = 0;
        int min = INT_MAX; // 从起点出发,油箱里的油量最小值
        for (int i = 0; i < gas.size(); i++) {
            int rest = gas[i] - cost[i];
            curSum += rest;
            if (curSum < min) {
                min = curSum;
            }
        }
        if (curSum < 0) return -1;  // 情况1
        if (min >= 0) return 0;     // 情况2
                                    // 情况3
        for (int i = gas.size() - 1; i >= 0; i--) {
            int rest = gas[i] - cost[i];
            min += rest;
            if (min >= 0) {
                return i;
            }
        }
        return -1;
    }
};
  • 时间复杂度: O(n)
  • 空间复杂度: O(1)

贪心算法(方法二)

可以换一个思路,首先如果总油量减去总消耗大于等于零那么一定可以跑完一圈,说明 各个站点的加油站 剩油量rest[i]相加一定是大于等于零的。

每个加油站的剩余量rest[i]为gas[i] - cost[i]。

i从0开始累加rest[i],和记为curSum,一旦curSum小于零,说明[0, i]区间都不能作为起始位置,因为这个区间选择任何一个位置作为起点,到i这里都会断油,那么起始位置从i+1算起,再从0计算curSum。

img

局部最优:当前累加rest[i]的和curSum一旦小于0,起始位置至少要是i+1,因为从i之前开始一定不行。全局最优:找到可以跑一圈的起始位置

局部最优可以推出全局最优,找不出反例,试试贪心!

C++代码如下:

class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        int curSum = 0;
        int totalSum = 0;
        int start = 0;
        for (int i = 0; i < gas.size(); i++) {
            curSum += gas[i] - cost[i];
            totalSum += gas[i] - cost[i];
            if (curSum < 0) {   // 当前累加rest[i]和 curSum一旦小于0
                start = i + 1;  // 起始位置更新为i+1
                curSum = 0;     // curSum从0开始
            }
        }
        if (totalSum < 0) return -1; // 说明怎么走都不可能跑一圈了
        return start;
    }
};
  • 时间复杂度:O(n)
  • 空间复杂度:O(1)

题目二:135. 分发糖果

135. 分发糖果

解题思路:

  1. 问题:需要根据孩子们的评分来分配糖果,确保评分高的孩子得到的糖果多于评分低的孩子。
  2. 确定策略:采用贪心算法,即在每一步选择当前最优的决策,不考虑其他可能的全局最优解。
  3. 初始化:为每个孩子分配至少一个糖果。
  4. 第一轮遍历(从前向后)
    • 从左到右遍历孩子们的评分。
    • 如果当前孩子的评分高于他左边的孩子,那么他应该得到比左边孩子多至少一个糖果。
  5. 局部到全局
    • 通过确保每个评分更高的孩子在糖果数量上也更高,逐步构建全局最优解。
  6. 第二轮遍历(从后向前,可选)
    • 从右到左再次遍历孩子们的评分。
    • 如果当前孩子的评分低于他右边的孩子,并且当前孩子的糖果数不比右边的孩子多,那么增加他的糖果数。
  7. 结束条件:完成两轮遍历后,得到的糖果分配方案即为最终解。
// 从前向后
for (int i = 1; i < ratings.size(); i++) {
    if (ratings[i] > ratings[i - 1]) candyVec[i] = candyVec[i - 1] + 1;
}

得到下图的列表:

135.分发糖果

再确定左孩子大于右孩子的情况(从后向前遍历)

// 从后向前
for (int i = ratings.size() - 2; i >= 0; i--) {
    if (ratings[i] > ratings[i + 1] ) {
        candyVec[i] = max(candyVec[i], candyVec[i + 1] + 1);
    }
}
img

整体代码如下:

class Solution {
public:
    int candy(vector<int>& ratings) {
        vector<int> candyVec(ratings.size(), 1);
        // 从前向后
        for (int i = 1; i < ratings.size(); i++) {
            if (ratings[i] > ratings[i - 1]) candyVec[i] = candyVec[i - 1] + 1;
        }
        // 从后向前
        for (int i = ratings.size() - 2; i >= 0; i--) {
            if (ratings[i] > ratings[i + 1] ) {
                candyVec[i] = max(candyVec[i], candyVec[i + 1] + 1);
            }
        }
        // 统计结果
        int result = 0;
        for (int i = 0; i < candyVec.size(); i++) result += candyVec[i];
        return result;
    }
};
  • 时间复杂度: O(n)
  • 空间复杂度: O(n)

题目三:860.柠檬水找零

860. 柠檬水找零

解题思路:

只需要维护三种金额的数量,5,10和20。

有如下三种情况:

  • 情况一:账单是5,直接收下。
  • 情况二:账单是10,消耗一个5,增加一个10
  • 情况三:账单是20,优先消耗一个10和一个5,如果不够,再消耗三个5

此时发现 情况一,情况二,都是固定策略,都不用我们来做分析了,而唯一不确定的其实在情况三。

情况三看似简单,实则蕴含贪心策略,即优先使用美元10解决当前找零问题。

因为美元10只能给账单20找零,而美元5可以给账单10和账单20找零,美元5更万能!

局部最优可以推出全局最优,并找不出反例,那么就试试贪心算法!

class Solution {
public:
    bool lemonadeChange(vector<int>& bills) {
        int five = 0, ten = 0, twenty = 0;
        for (int bill : bills) {
            // 情况一
            if (bill == 5) five++;
            // 情况二
            if (bill == 10) {
                if (five <= 0) return false;
                ten++;
                five--;
            }
            // 情况三
            if (bill == 20) {
                // 优先消耗10美元,因为5美元的找零用处更大,能多留着就多留着
                if (five > 0 && ten > 0) {
                    five--;
                    ten--;
                    twenty++; // 其实这行代码可以删了,因为记录20已经没有意义了,不会用20来找零
                } else if (five >= 3) {
                    five -= 3;
                    twenty++; // 同理,这行代码也可以删了
                } else return false;
            }
        }
        return true;
    }
};
  • 时间复杂度: O(n)
  • 空间复杂度: O(1)

小结:

​ 咋眼一看好像很复杂,分析清楚之后,会发现逻辑其实非常固定。

​ 这道题目可以告诉大家,遇到感觉没有思路的题目,可以静下心来把能遇到的情况分析一下,只要分析到具体情况了,一下子就豁然开朗了。

​ 如果一直陷入想从整体上寻找找零方案,就会把自己陷进去,各种情况一交叉,只会越想越复杂了。

题目四:406.根据身高重建队列

406. 根据身高重建队列

解题思路

本题有两个维度,h和k,看到这种题目一定要想如何确定一个维度,然后再按照另一个维度重新排列。
解题思路和135.分发糖果很类似。遇到两个维度权衡的时候,一定要先确定一个维度,再确定另一个维度。    
  • 避免同时考虑两个维度:如果同时考虑两个维度(k和h),可能会导致无法同时满足两个条件,从而顾此失彼。
  • 选择一个维度进行排序:在k和h两个维度中,选择先按身高h进行排序,且身高高的排在前面。这样做可以确定一个维度,即身高,确保前面的节点都比当前节点高。
  • 排序策略:在身高相同的情况下,选择k值较小的节点排在前面,这样可以在确定身高维度的同时,尽可能地满足另一个维度的条件。
406.根据身高重建队列

按照身高排序之后,优先按身高高的people的k来插入,后序插入节点也不会影响前面已经插入的节点,最终按照k的规则完成了队列。

所以在按照身高从大到小排序后:

局部最优:优先按身高高的people的k来插入。插入操作过后的people满足队列属性

全局最优:最后都做完插入操作,整个队列满足题目队列属性

局部最优可推出全局最优,找不出反例,那就试试贪心。

整体的插入步骤:

排序完的people: [[7,0], [7,1], [6,1], [5,0], [5,2], [4,4]]

插入的过程:

  • 插入[7,0]:[[7,0]]
  • 插入[7,1]:[[7,0],[7,1]]
  • 插入[6,1]:[[7,0],[6,1],[7,1]]
  • 插入[5,0]:[[5,0],[7,0],[6,1],[7,1]]
  • 插入[5,2]:[[5,0],[7,0],[5,2],[6,1],[7,1]]
  • 插入[4,4]:[[5,0],[7,0],[5,2],[6,1],[4,4],[7,1]]

此时就按照题目的要求完成了重新排列。

// 版本一
class Solution {
public:
    static bool cmp(const vector<int>& a, const vector<int>& b) {
        if (a[0] == b[0]) return a[1] < b[1];
        return a[0] > b[0];
    }
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
        sort (people.begin(), people.end(), cmp);
        vector<vector<int>> que;
        for (int i = 0; i < people.size(); i++) {
            int position = people[i][1];
            que.insert(que.begin() + position, people[i]);
        }
        return que;
    }
};
  • 时间复杂度:O( n l o g n + n 2 nlog n + n^2 nlogn+n2)
  • 空间复杂度:O(n)

动态数组的局限性:C++中的vector(动态数组)在插入元素时,如果当前容量不足以容纳新元素,就需要进行扩容操作。扩容通常涉及到申请一个更大的内存空间,并将现有数据复制到新的内存位置,这个过程是耗时的。

插入操作的效率问题:使用vector进行插入操作时,如果频繁触发扩容,那么单纯插入操作的时间复杂度可能达到O(n^2),甚至在多次拷贝的情况下,性能损耗可能会更加严重。

改成链表之后,C++代码如下:

// 版本二
class Solution {
public:
    // 身高从大到小排(身高相同k小的站前面)
    static bool cmp(const vector<int>& a, const vector<int>& b) {
        if (a[0] == b[0]) return a[1] < b[1];
        return a[0] > b[0];
    }
    vector<vector<int>> reconstructQueue(vector<vector<int>>& people) {
        sort (people.begin(), people.end(), cmp);
        list<vector<int>> que; // list底层是链表实现,插入效率比vector高的多
        for (int i = 0; i < people.size(); i++) {
            int position = people[i][1]; // 插入到下标为position的位置
            std::list<vector<int>>::iterator it = que.begin();
            while (position--) { // 寻找在插入位置
                it++;
            }
            que.insert(it, people[i]);
        }
        return vector<vector<int>>(que.begin(), que.end());
    }
};
  • 时间复杂度:O( n l o g n + n 2 nlog n + n^2 nlogn+n2)
  • 空间复杂度:O(n)

小结:

  • 其技巧都是确定一边然后贪心另一边,两边一起考虑,就会顾此失彼

  • 使用C++中的list(底层链表实现)比vector(数组)效率高得多。


总结

  • 两个维度的时候,要注意先确定一个维度,进而确定第二个维度
  • 不同的数据结构的使用,效率会差挺多

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

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

相关文章

openEuler:日志管理

日志介绍 概述 系统日志是一系列用于记录系统操作和活动进行的文件&#xff0c;这些日志对于监控和排查系统问题非常重要&#xff0c;因为它们可以提供有关系统行为、应用活动和安全事件的见解。系统日志还可以成为识别 Linux 系统中潜在安全弱点和漏洞的重要信息来源。通过分…

[米联客-XILINX-H3_CZ08_7100] FPGA程序设计基础实验连载-20 I2C MASTER控制器驱动设计

软件版本&#xff1a;VIVADO2021.1 操作系统&#xff1a;WIN10 64bit 硬件平台&#xff1a;适用 XILINX A7/K7/Z7/ZU/KU 系列 FPGA 实验平台&#xff1a;米联客-MLK-H3-CZ08-7100开发板 板卡获取平台&#xff1a;https://milianke.tmall.com/ 登录“米联客”FPGA社区 http…

pyautogui通过图像获取定位实现计算器自动计算

使用 pyautogui.locateCenterOnScreen 能够在屏幕上搜索给定图像的位置&#xff0c;并准确地返回该图像的中心点坐标。 &#x1f33f;使用 pyautogui 实现计算器自动计算 准备工作&#xff0c;把计算器的按钮截图保存下来。例如&#xff1a; 计算“75”&#xff0c;实现代码如…

【网络】WebSocket协议详解

WebSocket协议详解 一 、WebSocket 诞生背景二、WebSocket 特点三、WebSocket 的握手环节四、WebSokect 的数据格式1、 第一个字节2、第二个字节3、Masking-key4、playload Data5、一些注意细节 WebSocket 的官方文档 WebSocket 的中文文档(非官方) 一 、WebSocket 诞生背景 在…

深度学习基础—简单的卷积神经网络

3.1.卷积层 下面以卷积神经网络的某一层为例&#xff0c;详解一下网络的结构。 假设当前位于l层&#xff0c;则输入6*6*3的彩色图片&#xff0c;有两个3*3*3的过滤器&#xff0c;卷积操作后将输出2个4*4的图片。如果把过滤器看成权重w&#xff0c;卷积这一步操作其实就是w*a&am…

消息称华为纯血鸿蒙部分应用采用虚拟机方案

华为预计在11月发布正式版纯血鸿蒙&#xff0c;为了能够适配更多的App&#xff0c;官方也是有了新的解决方案。报道中提到&#xff0c;纯血鸿蒙设备对有些还没上架的应用会使用虚拟机方案过渡。据悉&#xff0c;华为的虚拟机方案作为过渡措施&#xff0c;首先能确保用户在鸿蒙系…

概率论与编程的联系及数据科学应用

目录 引言 第一章 概率模拟与编程实现 1.1 随机数生成与蒙特卡罗模拟 1.1.2 蒙特卡罗模拟 第二章 统计建模与数据分析 2.1 统计模型实现 2.2 概率图模型 第三章 概率论在机器学习中的应用 3.1 随机森林与决策树 3.2 贝叶斯分类器 总结与展望 引言 在大数据和人工智…

学习node.js 十 redis的基本语法

redis Redis&#xff08;Remote Dictionary Server&#xff09;是一个开源的内存数据结构存储系统&#xff0c;它提供了一个高效的键值存储解决方案&#xff0c;并支持多种数据结构&#xff0c;如字符串&#xff08;Strings&#xff09;、哈希&#xff08;Hashes&#xff09;、…

素数之和(c语言)

1./描述 //牛牛刚刚学了素数的定义&#xff1a;素数值指在大于1的自然数中&#xff0c;除了1和它本身以外不再有其他因数的自然数 //牛牛想知道在[l, r] 范围内全部素数的和 //输入描述&#xff1a; //输入两个正整数 l&#xff0c;r 表示闭区间范围 //输出描述&#xff1a; //…

sqli-labs靶场通关攻略 46-50

主页有sqli-labs靶场通关攻略 1-45 第四六关 less-46 步骤一&#xff1a;利用报错注入查询库 ?sort1 and updatexml(1,concat(0x7e,database(),0x7e),1) 步骤二&#xff1a;查询表名 ?sort1 and updatexml(1,concat(0x7e,(select group_concat(table_name)from informatio…

如何通过日志或gv$sql_audit,分析OceanBase运行时的异常SQL

本文作者&#xff1a;郑增权&#xff0c;爱可生 DBA 团队成员&#xff0c;OceanBase 和 MySQL 数据库技术爱好者。本文约 2000 字&#xff0c;预计阅读需要 8 分钟。 简介 在 OCP 云平台的 Top SQL 界面中&#xff0c;能观察到异常SQL&#xff0c;但这些SQL并未明确显示具体的…

防泄密的方法都有哪些?

一、防泄密的方法都有哪些&#xff1f;使用安全通讯工具&#xff1a;采用加密通讯工具&#xff0c;确保敏感信息在传输过程中不被窃取或篡改。定期安全审计&#xff1a;对系统和数据进行定期的安全审计和检查&#xff0c;发现潜在的泄密风险并及时处理。文件加密&#xff1a;对…

光伏电站的施工步骤

施工准备&#xff1a;在施工前&#xff0c;需要进行现场勘查&#xff0c;了解施工场地的地形、地貌、气候等情况&#xff0c;制定施工方案和安全措施。同时&#xff0c;还需要准备好施工所需的材料和设备&#xff0c;如光伏组件、支架、电缆、逆变器等 。基础施工&#xff1a;根…

“面试宝典:高频算法题目详解与总结”

干货分享&#xff0c;感谢您的阅读&#xff01; &#xff08;暂存篇---后续会删除&#xff0c;完整版和持续更新见高频面试题基本总结回顾&#xff08;含笔试高频算法整理&#xff09;&#xff09; 备注&#xff1a;引用请标注出处&#xff0c;同时存在的问题请在相关博客留言…

鸿蒙Harmony开发实战:自定义圆形组件-Canvas

在采用Java配合xml布局编写鸿蒙app页面的时候&#xff0c;发现sdk自带的Image组件并不能将图片设置成圆形&#xff0c;反复了翻阅了官方API手册&#xff08;主要查阅了Compont和Image相关的API&#xff09;&#xff0c;起初发现了一个setCornerRadius方法&#xff0c;于是想着将…

高职院校人工智能训练师边缘计算实训室建设方案

一、引言 随着人工智能技术的飞速发展&#xff0c;边缘计算在提升数据处理效率、降低延迟、保护数据安全等方面展现出巨大潜力。高职院校作为技能型人才培养的重要基地&#xff0c;建设人工智能训练师边缘计算实训室&#xff0c;旨在培养掌握前沿技术、具备实战能力的复合型人才…

pnpm国内源设置

一、背景 在国内使用pnpm时&#xff0c;由于网络问题&#xff0c;经常会遇到速度慢或无法访问的问题。为了提高效率&#xff0c;可以将pnpm的源设置为国内的镜像源。以下是一些常用的国内pnpm镜像源以及如何设置它们的方法。 二、国内可用源 2.1 淘宝pnpm源 https://registry…

神经网络卷积层

一、卷积操作 对应位置相乘相加&#xff0c;最终组成一个新的矩阵&#xff0c;实现了降维。 二、代码 import torch import torchvision from torch import nn from torch.nn import Conv2d from torch.utils.data import DataLoaderdataset torchvision.datasets.CIFAR10(&…

三级_网络技术_54_应用题

一、 请根据下图所示网络结构回答下列问题。 1.填写路由器RG的路由表项。 目的网络/掩码长度输出端口__________S0&#xff08;直接连接&#xff09;__________S1&#xff08;直接连接&#xff09;__________S0__________S1__________S0__________S1 2.如果将10.10.67.128/2…

C++----简单了解vector

大家好&#xff0c;今天我们来讲讲与string相似的向量类型。之所以说他们是相似的原因是他们其中的数据类型有些效果都是一样的。当然大家不能说&#xff0c;既然是差不多的干嘛还有一个这个啊。不如直接用string就可以了。当然世界名言存在即合理。既然我们都能想到的东西&…