134. 加油站(力扣LeetCode)

文章目录

  • 134. 加油站
    • 暴力枚举(超时)
      • 代码一
      • 代码二(优化)
    • 贪心算法
      • 方法一
      • 方法二

134. 加油站

在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。

你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 cost[i] 升。你从其中的一个加油站出发,开始时油箱为空。

给定两个整数数组 gas 和 cost ,如果你可以按顺序绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1 。如果存在解,则 保证 它是 唯一 的。

示例 1:

输入: gas = [1,2,3,4,5], cost = [3,4,5,1,2]
输出: 3
解释:
从 3 号加油站(索引为 3 处)出发,可获得 4 升汽油。此时油箱有 = 0 + 4 = 4 升汽油
开往 4 号加油站,此时油箱有 4 - 1 + 5 = 8 升汽油
开往 0 号加油站,此时油箱有 8 - 2 + 1 = 7 升汽油
开往 1 号加油站,此时油箱有 7 - 3 + 2 = 6 升汽油
开往 2 号加油站,此时油箱有 6 - 4 + 3 = 5 升汽油
开往 3 号加油站,你需要消耗 5 升汽油,正好足够你返回到 3 号加油站。
因此,3 可为起始索引。

示例 2:

输入: gas = [2,3,4], cost = [3,4,3]
输出: -1
解释:
你不能从 0 号或 1 号加油站出发,因为没有足够的汽油可以让你行驶到下一个加油站。
我们从 2 号加油站出发,可以获得 4 升汽油。 此时油箱有 = 0 + 4 = 4 升汽油
开往 0 号加油站,此时油箱有 4 - 3 + 2 = 3 升汽油
开往 1 号加油站,此时油箱有 3 - 3 + 3 = 3 升汽油
你无法返回 2 号加油站,因为返程需要消耗 4 升汽油,但是你的油箱只有 3 升汽油。
因此,无论怎样,你都不可能绕环路行驶一周。

提示:

  • gas.length == n
  • cost.length == n
  • 1 <= n <= 105
  • 0 <= gas[i], cost[i] <= 104

暴力枚举(超时)

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

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

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

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

代码一

class Solution {
public:int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {// 遍历每个加油站作为起点for(int i = 0; i < gas.size(); i++) {// 如果当前加油站的汽油不足以到达下一个加油站,则跳过if(gas[i] < cost[i])continue;else {// sum用于存储从当前加油站出发,行驶过程中的油量净增加值int sum = gas[i] - cost[i];// 计算下一个加油站的索引int n = (i + 1) % gas.size();// 如果下一个加油站就是当前加油站,意味着只有一个加油站,直接返回当前加油站的索引if(n == i)return i;// 否则,继续尝试从当前加油站出发,尝试绕一圈回到起点while(n != i) {// 累加从当前加油站到下一个加油站的油量净增加值sum += gas[n] - cost[n];// 如果中途油量不足以到达下一个加油站,跳出循环if(sum < 0)break;else {// 计算起点加油站的前一个加油站索引int z = i - 1;if(i - 1 < 0)z = gas.size() - 1;// 如果下一个加油站是起点加油站的前一个加油站,意味着已经成功绕一圈,返回当前加油站索引if(n == z)return i;}// 更新下一个加油站的索引n++;n = n % gas.size();}}}// 如果遍历完所有加油站都无法找到能绕一圈回到起点的加油站,返回-1return -1;}
};

代码二(优化)

这段代码是针对“加油站”问题的一个解决方案。该问题要求在一条环形路线上找到一个加油站,从该站出发,汽车能够绕环形路线行驶一周并回到起点。下面是对该解决方案的详细注释说明:

class Solution {
public:int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {// 遍历每一个加油站作为可能的起点for(int i=0; i<gas.size(); i++) {// 初始化sum为从当前加油站出发的油量与消耗的差值int sum = gas[i] - cost[i];// 计算下一个加油站的索引。因为是环形,所以使用取余操作int index = (i + 1) % gas.size();// 当当前的油量剩余大于0,并且还没有回到起点加油站时,继续行驶while(sum > 0 && index != i) {// 更新sum,加上在当前加油站加的油,并减去到下一站的消耗sum += gas[index] - cost[index];// 更新下一个加油站的索引,仍然使用取余操作保证环形属性index = (index + 1) % gas.size();}// 如果最后的油量sum不小于0,并且已经回到了起点,这表明找到了一个可行的起点if(sum >= 0 && index == i) return i;}// 如果遍历完所有加油站都没有找到可行的起点,返回-1return -1;}
};

解题思路简述:

  1. 尝试每一个加油站作为起点: 由于不知道哪个加油站能够使汽车成功绕环形路线一周,代码首先尝试将每一个加油站设为起点。

  2. 计算油量差值: 对于每一个尝试作为起点的加油站,计算从该站出发至下一站的油量剩余(即当前加油站的油量减去到下一站的油耗)。

  3. 环形行驶检查: 从尝试的起始加油站开始,根据油量差值判断是否能够继续前往下一个加油站。这一过程一直持续到汽车要么无法继续前进(油量差值变为负),要么回到了起点。

  4. 判断成功条件: 如果最终汽车油量不小于0并且回到了起点,意味着从当前尝试的起点出发可以成功绕环形路线一周。返回这个加油站的索引。

  5. 全部尝试失败: 如果所有加油站作为起点都无法满足条件,则返回-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) {// curSum 用来记录从起点出发的总油量净增加值int curSum = 0;// min 用来追踪从起点开始,油箱中油量的最小值int min = INT_MAX; // 初始化为最大整数,以便于后续的比较// 遍历所有加油站,计算从起点出发的总油量净增加值,并找出最小油量值for (int i = 0; i < gas.size(); i++) {// rest 是当前加油站的油量与消耗油量的差值int rest = gas[i] - cost[i];// curSum 累加所有油站的油量净增加值curSum += rest;// 更新最小油量值if (curSum < min) {min = curSum;}}// 情况1: 如果最终油量净增加值小于0, 说明无法绕环路行驶一周if (curSum < 0) return -1;// 情况2: 如果从起点出发的油量最小值大于或等于0, 则起点为0的位置可以行驶一周if (min >= 0) return 0;// 情况3: 如果上述情况都不满足,需要从后向前遍历,找到新的出发点for (int i = gas.size() - 1; i >= 0; i--) {// 计算从当前加油站出发的油量净增加值int rest = gas[i] - cost[i];// 从后向前累加油量净增加值,并更新最小油量值min += rest;// 一旦最小油量值大于等于0, 则当前加油站的位置可以作为出发点if (min >= 0) {return i; // 返回该加油站作为新的出发点}}// 如果没有合适的出发点,返回-1return -1;}
};

代码分析了三种情况:

  1. 情况1:如果在遍历所有加油站之后,总油量净增加值(curSum)小于0,则说明油量不足以绕环路行驶一周,此时应该返回-1。

  2. 情况2:如果从起点到任何加油站的过程中,油箱中油量的最小值(min)大于或等于0,则说明可以从0位置开始,顺利绕环路行驶一周,此时应该返回0。

  3. 情况3:如果上述两种情况都不满足,即总油量是足够的,但是油箱中油量的最小值(min)小于0,则需要从后向前遍历加油站。这是因为如果存在一个解,那么油箱中油量最小值(min)之后的加油站即为新的出发点。在这个循环中,我们通过从后向前累加油量差值来更新min,一旦min非负,就找到了新的出发点。

这个算法的关键在于利用了油量净增加的累加和与最小值来决定出发点。如果全程油量净增加为负,那么无论如何都无法完成环路行驶。如果全程油量净增加为正,那么必定存在一个加油站,它的下一个加油站是可以作为出发点的,因为从这一点开始油量将始终为正直到行驶结束。

其实我不认为这种方式是贪心算法,因为没有找出局部最优,而是直接从全局最优的角度上思考问题。

但这种解法又说不出是什么方法,这就是一个从全局角度选取最优解的模拟操作。

所以对于本解法是贪心,我持保留意见!

但不管怎么说,解法毕竟还是巧妙的,不用过于执着于其名字称呼。

方法二

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

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

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

如图:
在这里插入图片描述
那么为什么一旦[0,i] 区间和为负数,起始位置就可以是i+1呢,i+1后面就不会出现更大的负数?

如果出现更大的负数,就是更新i,那么起始位置又变成新的i+1了。

那有没有可能 [0,i] 区间 选某一个作为起点,累加到 i这里 curSum是不会小于零呢? 如图:
在这里插入图片描述
如果 curSum<0 说明 区间和1 + 区间和2 < 0, 那么 假设从上图中的位置开始计数curSum不会小于0的话,就是 区间和2>0。

区间和1 + 区间和2 < 0 同时 区间和2>0,只能说明区间和1 < 0, 那么就会从假设的箭头初就开始从新选择其实位置了。

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

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

C++代码如下:

class Solution {
public:int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {// 定义sum来记录全程的油量差(加油量-消耗量),用以判断整个环形路线是否能够走完int sum=0;// 定义cur来记录当前油量差,用以判断从某个加油站出发是否能到达下一个加油站int cur=0;// 定义start来记录起始加油站的位置,默认为0,即第一个加油站int start=0;// 遍历所有加油站for(int i=0;i<gas.size();i++){// 更新当前油量差,表示从这个加油站出发到达下一个加油站后的油量变化cur+=gas[i]-cost[i];// 同时更新全程油量差sum+=gas[i]-cost[i];// 如果当前油量差小于0,意味着无法从当前加油站到达下一个加油站if(cur<0){// 重置当前油量差,从下一个加油站重新开始计算cur=0;// 将下一个加油站设为新的起点start=i+1;}}// 如果全程油量差小于0,意味着无论从哪个加油站出发,都无法完成全程行驶,返回-1if(sum<0) return -1;// 如果全程油量差不小于0,返回能够作为起点的加油站位置return start;}
};

这个解决方案的核心思想是使用贪心算法的策略,通过一次遍历来确定能否绕环路行驶一周,以及从哪个加油站出发。

  1. 全程油量差(sum): 确定整个环路是否能走完。如果sum最终小于0,意味着加油量总和小于消耗量总和,因此不可能完成环路行驶。

  2. 当前油量差(cur): 用来判断从当前起点出发,能否顺利到达下一个加油站。如果cur在任何时点小于0,说明从当前起点无法走完全程,需要将下一个加油站设为新的起点,并重新计算。

  3. 起始加油站(start): 记录在满足条件下,可以作为行驶全程起点的加油站位置。

通过这种方式,代码在遍历一次所有加油站的过程中,有效地判断出了是否存在可行解,以及可行解的具体位置。

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

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

相关文章

ng发布静态资源 发布项目 发布数据

描述&#xff1a;把一个项目或者数据发布出来&#xff0c;通过http的形式访问&#xff0c;比如发布一个js文件&#xff0c;用http://localhost:6060/data/jquery/jquery.min.js访问。 步骤&#xff1a;配置nginx.conf文件&#xff0c;nginx.conf位于conf目录下&#xff0c;在se…

ROS机器人虚拟仿真挑战赛本地电脑环境配置测试

预备基础 此案例需要完成&#xff1a; ROS机器人虚拟仿真挑战赛本地电脑环境配置记录-CSDN博客 ROS机器人虚拟仿真挑战赛本地电脑环境配置个人问题汇总-CSDN博客 命令测试 在不同的终端窗口分别输入&#xff1a; 标签1&#xff1a; roslaunch tianracer_gazebo demo_tian…

分享|大数据信用风险测评多久做一次比较好?

大数据信用风险测评多久做一次比较好?对于个人大数据信用风险测评&#xff0c;一般来说&#xff0c;多久做一次并没有固定的时间间隔。这取决于许多因素&#xff0c;包括个人信用状况、数据更新频率、个人需求等等。 首先&#xff0c;个人的信用状况是决定测评频率的一个重要因…

成都百洲文化传媒有限公司电商新浪潮的领航者

在当今电商行业风起云涌的时代&#xff0c;成都百洲文化传媒有限公司以其独特的视角和专业的服务&#xff0c;成为了众多商家争相合作的伙伴。今天&#xff0c;就让我们一起走进百洲文化的世界&#xff0c;探索其背后的成功密码。 一、百洲文化的崛起之路 成都百洲文化传媒有限…

各类主流电商API商品采集接口的权限控制和功能权限控制

主流电商平台的API接口类型 参数说明 通用参数说明 url说明 /平台/API类型/ 平台&#xff1a;淘宝&#xff0c;京东等&#xff0c; API类型:[item_search,item_get,item_search_shop等]version:API版本key:调用key,测试key:test_api_keysecret:调用secret,测试secret:(不用填写…

Unity定时播放音乐

一、需求 需要定时在早上8:50&#xff0c;中午12:00&#xff0c;下午13:10定时播放音乐 二、实现步骤 依次在unity创建背景图、主文字提示、时间文字提示、音量控制器及音量文字提示、退出按钮、播放按钮&#xff0c;暂停按钮 在Canvas下创建一个Script脚本&#xff1a;获取…

【光标精灵】让您享受鼠标皮肤多样化快捷更换

鼠标作为我们日常使用频率最高的“小伙伴”&#xff0c;扮演着至关重要的角色。尤其是在女生群体中&#xff0c;对于打造一个个性化、可爱的电脑桌面和软件界面的需求日益增长。然而&#xff0c;尽管电脑默认提供了一些可更换的光标图案&#xff0c;但仍显得有些单调和呆板。想…

从Spring进化为SpringBoot

目录 零.SpringBootApplication 一.起步依赖 二.自动配置 三.自动配置的原理 1.装配常见方案 【1】方案1&#xff1a;ComponentScan 组件扫描 【2】方案2&#xff1a;Import 导入 【3】使用第三方依赖提供的 EnableXxxxx注解 2.自动装配原理 3.实现starter 四.内置T…

虚拟直播赋能文旅,蓝海创意云亮相文旅虚拟现实应用推广交流活动

3月21日&#xff0c;由文化和旅游部产业发展司主办&#xff0c;中国信息通信研究院、北京市石景山区文化和旅游局、中国动漫集团有限公司承办的文化和旅游虚拟现实应用推广交流活动在首钢一高炉SoReal科幻乐园33 Meta Club举办。蓝海创意云应邀参与此次活动&#xff0c;携vLive…

MySQL索引的创建与基本用法

文章目录 MySQL索引MySQL索引的类型与创建方法基础索引 唯一索引**唯一索引的创建** ******注意唯一索引允许有空值&#xff08;注意和主键不同&#xff09;。如果是用组合索引创建&#xff0c;则列值的组合必须唯一。添加唯一键将自动创建唯一索引。*** ***主键索引组合索引**…

Java毕业设计 基于springboot医院挂号系统 医院管理系统

Java毕业设计 基于springboot医院挂号系统 医院管理系统 springboot医院挂号系统 医院管理系统 功能介绍 用户&#xff1a;登录 首页 个人资料 修改密码 门诊管理 用户挂号 医生&#xff1a;登录 首页 个人资料 修改密码 门诊管理: 用户挂号 处方划价 项目划价 项目缴费 项目…

C++多线程并发学完后,该学什么呢?

概述 如何安排知识点的学习次序&#xff0c;有很多做法&#xff0c;下面提供一种思路。 答&#xff1a;C在多线程学完后&#xff0c;可以学网络。 相当于这么一个学习及实际开发中常见演进过程&#xff1a; 单线程、无网络的程序&#xff1a; 一家公司&#xff0c;一个员工…

多级页表查询

说明一下这个三级页表的查询&#xff0c;会需要上面的L2,L1,L0 如果在二级页表level就是2&#xff0c;PGSHIFT是12&#xff0c;那么就是往左移129*2位置&#xff0c;在&9bit就得到L2&#xff0c;其他以此类推 也表查询&#xff0c;首先有跟页表的地址pagetable&#xff0c;…

测径仪:大小通吃的“直径判官”

嗨&#xff0c;大家好&#xff01;今天我要给你们介绍一个超级厉害的玩意儿——在线测径仪&#xff01;它可不是一般的测量工具&#xff0c;它是那个能让直径无处遁形的“直径判官”&#xff01; 想象一下&#xff0c;有了在线测径仪&#xff0c;你就像是拥有了一双超级敏锐的“…

elasticsearch的数据搜索

DSL查询文档 elasticsearch的查询依然是基于JSON风格的DSL来实现的。 Elasticsearch提供了基于JSON的DSL(Domain Specific Language)来定义查询。常见的查询类型包括: 查询所有:查询出所有数据,一般测试用。例如:match_all 全文检索(full text)查询:利用分词器对用户…

docker容器下部署hbase并在springboot中通过jdbc连接

我在windows的docker中部署了一个hbase服务&#xff0c;然后用springboot连接到此服务并访问数据。 详情可参考项目中的README.md。项目中提供了用于构建镜像的dockerfile&#xff0c;以及测试代码。 项目连接&#xff1a;https://gitee.com/forgot940629/hbase_phoenix_spring…

【Windows 常用工具系列 15 -- VMWARE ubuntu 安装教程】

文章目录 安装教程镜像下载 工具安装 安装教程 安装教程参考链接&#xff1a;https://blog.csdn.net/Python_0011/article/details/131619864 https://linux.cn/article-15472-1.html 激活码 VMware 激活码连接&#xff1a;https://www.haozhuangji.com/xtjc/180037874.html…

一下想要邮寄很多快递,该怎么邮寄呢?

我们都知道淘宝网上开店的经常会邮寄很多快递&#xff0c;但是这么多快递不可能一下子拿到快递驿站去邮寄吧&#xff0c;但是快递员又不上门取件可怎么办呀&#xff0c;这可真是让人伤透了脑筋了&#xff0c; 我们都知道我们所在的区域一般都会有快递员来承揽我们所在区域的快递…

Jenkins安装 Linux 更换镜像 安装插件

Jenkins安装 Linux 更换镜像 安装插件 前言 下面叙述了三种jenkins安装的方式,jenkins安装之前必须有java环境因为他是java写的… yum安装只能安装最新版本的jenkins,但是jenkins是java写的所以他强依赖java版本,当你的服务器的java版本与jenkins版本冲突时还需要给jenkins重…

烟火AI识别检测算法在新能源汽车充电桩站点的应用方案

新能源汽车作为现代科技与环保理念的完美结合&#xff0c;其普及和应用本应带给人们更加便捷和绿色的出行体验。然而&#xff0c;近年来新能源汽车充电火灾事故的频发&#xff0c;无疑给这一领域投下了巨大的阴影。这不禁让人深思&#xff0c;为何这一先进的交通工具在充电过程…