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;}
};
解题思路简述:
-
尝试每一个加油站作为起点: 由于不知道哪个加油站能够使汽车成功绕环形路线一周,代码首先尝试将每一个加油站设为起点。
-
计算油量差值: 对于每一个尝试作为起点的加油站,计算从该站出发至下一站的油量剩余(即当前加油站的油量减去到下一站的油耗)。
-
环形行驶检查: 从尝试的起始加油站开始,根据油量差值判断是否能够继续前往下一个加油站。这一过程一直持续到汽车要么无法继续前进(油量差值变为负),要么回到了起点。
-
判断成功条件: 如果最终汽车油量不小于0并且回到了起点,意味着从当前尝试的起点出发可以成功绕环形路线一周。返回这个加油站的索引。
-
全部尝试失败: 如果所有加油站作为起点都无法满足条件,则返回-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:如果在遍历所有加油站之后,总油量净增加值(
curSum
)小于0,则说明油量不足以绕环路行驶一周,此时应该返回-1。 -
情况2:如果从起点到任何加油站的过程中,油箱中油量的最小值(
min
)大于或等于0,则说明可以从0位置开始,顺利绕环路行驶一周,此时应该返回0。 -
情况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;}
};
这个解决方案的核心思想是使用贪心算法的策略,通过一次遍历来确定能否绕环路行驶一周,以及从哪个加油站出发。
-
全程油量差(
sum
): 确定整个环路是否能走完。如果sum
最终小于0,意味着加油量总和小于消耗量总和,因此不可能完成环路行驶。 -
当前油量差(
cur
): 用来判断从当前起点出发,能否顺利到达下一个加油站。如果cur
在任何时点小于0,说明从当前起点无法走完全程,需要将下一个加油站设为新的起点,并重新计算。 -
起始加油站(
start
): 记录在满足条件下,可以作为行驶全程起点的加油站位置。
通过这种方式,代码在遍历一次所有加油站的过程中,有效地判断出了是否存在可行解,以及可行解的具体位置。
相关文章:

134. 加油站(力扣LeetCode)
文章目录 134. 加油站题目描述暴力枚举(超时)代码一代码二(优化) 贪心算法方法一方法二 134. 加油站 题目描述 在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gas[i] 升。 你有一辆油箱容量无限的的汽车&…...

XSKY 智能存储,助力“数据要素 X”先进制造
3 月 21-22 日,主题为“突破 智行”的 IMC2024 第七届中国智造数字科技峰会在重庆召开。作为在先进制造领域拥有领先存储解决方案以及众多应用实践的企业,星辰天合受邀参加了此次峰会并荣获大会颁发的“最佳存储解决方案奖”。同时,星辰天合先…...

数据挖掘与分析学习笔记
一、Numpy NumPy(Numerical Python)是一种开源的Python库,专注于数值计算和处理多维数组。它是Python数据科学和机器学习生态系统的基础工具包之一,因为它高效地实现了向量化计算,并提供了对大型多维数组和矩阵的支持…...
linux docker镜像初始化
linux docker镜像初始化 简介 有的镜像内部使用的linux系统特别精简,许多常用命令无法安装,导致排查问题较为困难。 可以使用cat /etc/os-release查看容器使用的linux版本,再进行一些常用操作的初始化。 Debian # 设置镜像源 RUN rm -f /…...

专业140+总分410+南京大学851信号与系统考研经验南大电子信息与通信集成,电通,真题,大纲,参考书。
今年分数出来还是有点小激动,专业851信号与系统140(感谢Jenny老师辅导和全程悉心指导,答疑),总分410,梦想的南大离自己越来越近,马上即将复试,心中慌的一p,闲暇之余&…...
. ./ bash dash source 这五种执行shell脚本方式 区别
实际上,., ./, bash, dash, source 是五种不同的方式来执行 shell 脚本,它们之间有一些区别。 .(点号)或 source 命令:这两个命令是等价的,它们都是 Bash shell 内置的命令。它们用于在当前 shell 环境中执行脚本。当使用 . script.sh 或 source script.sh 命令来执行脚本…...
【React 】React 性能优化的手段有哪些?
1. 是什么 React凭借virtual DOM和diff算法拥有高效的性能,但是某些情况下,性能明显可以进一步提高 在前面文章中,我们了解到类组件通过调用setState方法,就会导致render ,父组件一旦发生render渲染,子组件一定也会执…...

3.22网络编程小项目
基于UDP的网络聊天室 项目需求: 如果有用户登录,其他用户可以收到这个人的登录信息如果有人发送信息,其他用户可以收到这个人的群聊信息如果有人下线,其他用户可以收到这个人的下线信息服务器可以发送系统信息 服务器 #includ…...

Git原理及使用
1、Git初识 Git是一种版本控制器: 对于同一份文件,做多次改动,Git会记录每一次改动前后的文件。 通俗的讲就是⼀个可以记录⼯程的每⼀次改动和版本迭代的⼀个管理系统,同时也⽅便多⼈协同作业。 注意: Git其实只能跟踪⽂本⽂件的改动,⽐如TXT⽂件,⽹⻚,所有的程序代码…...

Milvus 向量数据库介绍及使用
一、Milvus 介绍及安装 Milvus 于 2019 年创建,其目标只有一个:存储、索引和管理由深度神经网络和其他机器学习 (ML) 模型生成的大量嵌入向量。它具备高可用、高性能、易拓展的特点,用于海量向量数据的实时召回。 作为专门为处理输入向量查…...

STP环路避免实验(华为)
思科设备参考:STP环路避免实验(思科) 一,技术简介 Spanning Tree Protocol(STP),即生成树协议,是一种数据链路层协议。主要作用是防止二层环路,并自适应网络变化和故障…...

二、SpringBoot3 配置文件
本章概要 统一配置管理概述属性配置文件使用YAML 配置文件使用批量配置文件注入多环境配置和使用 2.1 统一配置管理概述 SpringBoot工程下,进行统一的配置管理,你想设置的任何参数(端口号、项目根路径、数据库连接信息等等)都集中到一个固定…...

二、阅读器的开发(初始)-- 2、阅读器开发
1、epubjs核心工作原理 1.1 epubjs的核心工作原理解析 epub电子书,会通过epubjs去实例化一个Book对象,Book对象会对电子书进行解析。Book对象可以通过renderTo方法去生成一个Rendition对象,Rendition主要负责电子书的渲染,通过R…...

【QT入门】 Qt自定义信号后跨线程发送信号
往期回顾: 【QT入门】 lambda表达式(函数)详解-CSDN博客 【QT入门】 Qt槽函数五种常用写法介绍-CSDN博客 【QT入门】 Qt实现自定义信号-CSDN博客 【QT入门】 Qt自定义信号后跨线程发送信号 由于Qt的子线程是无法直接修改ui,需要发送信号到ui线程进行修改…...

51单片机学习笔记7 串转并操作方法
51单片机学习笔记7 串转并操作方法 一、串转并操作简介二、74HC595介绍1. **功能**:2. **引脚**:3. **工作原理**:4. 开发板原理图(1)8*8 LED点阵:(2)74HC595 串转并: 三…...

微服务cloud--抱团取暖吗 netflix很多停更了
抱团只会卷,卷卷也挺好的 DDD 高内聚 低耦合 服务间不要有业务交叉 通过接口调用 分解技术实现的复杂性,围绕业务概念构建领域模型;边界划分 业务中台: 数据中台: 技术中台: 核心组件 eureka&#x…...
牛客笔试|美团2024春招第一场【测试方向】
第一题:小美的数组询问 小美拿到了一个由正整数组成的数组,但其中有一些元素是未知的(用 0 来表示)。 现在小美想知道,如果那些未知的元素在区间 [l, r] 范围内随机取值的话,数组所有元素之和的最小值和最大…...
Docker搭建LNMP环境实战(一):前言
缘起:不久前学习了Docker相关知识,并在Docker环境下学习了LNMP环境的搭建。由于网上的文章大多没有翔实、可行的案例,很多文章都是断章取义,所以,期间踩了太多太多的坑,初学者想要真正顺利地搭建一套环境起…...

SCI一区 | Matlab实现PSO-TCN-BiGRU-Attention粒子群算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测
SCI一区 | Matlab实现PSO-TCN-BiGRU-Attention粒子群算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测 目录 SCI一区 | Matlab实现PSO-TCN-BiGRU-Attention粒子群算法优化时间卷积双向门控循环单元融合注意力机制多变量时间序列预测预测效果基本介绍模型描述…...

界面控件DevExpress ASP.NET Ribbon组件 - 完美复刻Office 365体验!
无论用户是喜欢传统工具栏菜单外观、样式,还是想在下一个项目中复制Office 365 web UI,DevExpress ASP.NET都提供了所需要的工具,帮助用户打造更好的应用程序界面。 P.S:DevExpress ASP.NET Web Forms Controls拥有针对Web表单&a…...
React 第五十五节 Router 中 useAsyncError的使用详解
前言 useAsyncError 是 React Router v6.4 引入的一个钩子,用于处理异步操作(如数据加载)中的错误。下面我将详细解释其用途并提供代码示例。 一、useAsyncError 用途 处理异步错误:捕获在 loader 或 action 中发生的异步错误替…...
论文解读:交大港大上海AI Lab开源论文 | 宇树机器人多姿态起立控制强化学习框架(二)
HoST框架核心实现方法详解 - 论文深度解读(第二部分) 《Learning Humanoid Standing-up Control across Diverse Postures》 系列文章: 论文深度解读 + 算法与代码分析(二) 作者机构: 上海AI Lab, 上海交通大学, 香港大学, 浙江大学, 香港中文大学 论文主题: 人形机器人…...

练习(含atoi的模拟实现,自定义类型等练习)
一、结构体大小的计算及位段 (结构体大小计算及位段 详解请看:自定义类型:结构体进阶-CSDN博客) 1.在32位系统环境,编译选项为4字节对齐,那么sizeof(A)和sizeof(B)是多少? #pragma pack(4)st…...

云启出海,智联未来|阿里云网络「企业出海」系列客户沙龙上海站圆满落地
借阿里云中企出海大会的东风,以**「云启出海,智联未来|打造安全可靠的出海云网络引擎」为主题的阿里云企业出海客户沙龙云网络&安全专场于5.28日下午在上海顺利举办,现场吸引了来自携程、小红书、米哈游、哔哩哔哩、波克城市、…...
C++中string流知识详解和示例
一、概览与类体系 C 提供三种基于内存字符串的流,定义在 <sstream> 中: std::istringstream:输入流,从已有字符串中读取并解析。std::ostringstream:输出流,向内部缓冲区写入内容,最终取…...
三体问题详解
从物理学角度,三体问题之所以不稳定,是因为三个天体在万有引力作用下相互作用,形成一个非线性耦合系统。我们可以从牛顿经典力学出发,列出具体的运动方程,并说明为何这个系统本质上是混沌的,无法得到一般解…...

如何在最短时间内提升打ctf(web)的水平?
刚刚刷完2遍 bugku 的 web 题,前来答题。 每个人对刷题理解是不同,有的人是看了writeup就等于刷了,有的人是收藏了writeup就等于刷了,有的人是跟着writeup做了一遍就等于刷了,还有的人是独立思考做了一遍就等于刷了。…...
JS设计模式(4):观察者模式
JS设计模式(4):观察者模式 一、引入 在开发中,我们经常会遇到这样的场景:一个对象的状态变化需要自动通知其他对象,比如: 电商平台中,商品库存变化时需要通知所有订阅该商品的用户;新闻网站中࿰…...

莫兰迪高级灰总结计划简约商务通用PPT模版
莫兰迪高级灰总结计划简约商务通用PPT模版,莫兰迪调色板清新简约工作汇报PPT模版,莫兰迪时尚风极简设计PPT模版,大学生毕业论文答辩PPT模版,莫兰迪配色总结计划简约商务通用PPT模版,莫兰迪商务汇报PPT模版,…...
Web中间件--tomcat学习
Web中间件–tomcat Java虚拟机详解 什么是JAVA虚拟机 Java虚拟机是一个抽象的计算机,它可以执行Java字节码。Java虚拟机是Java平台的一部分,Java平台由Java语言、Java API和Java虚拟机组成。Java虚拟机的主要作用是将Java字节码转换为机器代码&#x…...