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();
                }
            }
        }
        // 如果遍历完所有加油站都无法找到能绕一圈回到起点的加油站,返回-1
        return -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;
        }
        
        // 如果遍历完所有加油站都没有找到可行的起点,返回-1
        return -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; // 返回该加油站作为新的出发点
            }
        }
        // 如果没有合适的出发点,返回-1
        return -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,意味着无论从哪个加油站出发,都无法完成全程行驶,返回-1
        if(sum<0) return -1;
        // 如果全程油量差不小于0,返回能够作为起点的加油站位置
        return start;
    }
};

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

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

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

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

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

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【机器学习入门 】支持向量机

系列文章目录 第1章 专家系统 第2章 决策树 第3章 神经元和感知机 识别手写数字——感知机 第4章 线性回归 第5章 逻辑斯蒂回归和分类 前言 支持向量机(Support Vector Machine) 于1995年发表&#xff0c;由于其优越的性能和广泛的适用性&#xff0c;成为机器学习的主流技术&…

【11】工程化

一、为什么需要模块化 当前端工程到达一定规模后,就会出现下面的问题: 全局变量污染 依赖混乱 上面的问题,共同导致了代码文件难以细分 模块化就是为了解决上面两个问题出现的 模块化出现后,我们就可以把臃肿的代码细分到各个小文件中,便于后期维护管理 前端模块化标准…

活用 C语言之union的精妙之用

一、union的基本定义 Union的中文叫法又被称为共用体、联合或者联合体。它的定义方式与结构体相同,但意义却与结构体完全不同。下面是union的定义格式: union 共用体名 {成员列表}共用体变量名;它与结构体的定义方式相同,但区别在于共用体中的成员的起始地址都是相同的,…

Android Studio Gradle设置查看全部task

如果你在 Android Studio 的 Gradle 窗口中看不到所有的任务&#xff0c;你可以尝试以下步骤来解决这个问题 android studio 版本&#xff1a; Android Studio Iguana | 2023.2.1 Build #AI-232.10227.8.2321.11479570, built on February 22, 2024 打开 Android Studio 的设置…

【CSP】2020-12-3 带配额的文件系统 100分完整代码 最长的大模拟 使用指针优化数据结构

2020-12-3 带配额的文件系统 最长的大模拟 使用指针优化数据结构 索引2020-12-3 带配额的文件系统 最长的大模拟 使用指针优化数据结构思路遇到的问题(学到的东西)40分stl代码acwing 15/15 csp官网40分代码100分完整代码 索引 历年CSP认证考试真题题解总汇持续更新 2020-12-3…

框架结构模态分析/动力时程分析Matlab有限元编程 【Matlab源码+PPT讲义】|梁单元|地震时程动画|结果后处理|地震弹性时程分析| 隐式动力学

专栏导读 作者简介&#xff1a;工学博士&#xff0c;高级工程师&#xff0c;专注于工业软件算法研究本文已收录于专栏&#xff1a;《有限元编程从入门到精通》本专栏旨在提供 1.以案例的形式讲解各类有限元问题的程序实现&#xff0c;并提供所有案例完整源码&#xff1b;2.单元…

Bytebase 2.14.1 - 分支 (Branching) 功能支持 Oracle

&#x1f680; 新功能 分支 (Branching) 功能支持 Oracle。为 SQL 编辑器添加了项目选择器。 新增 SQL 审核规范&#xff1a; 禁止混合 DDL、DML 语句。禁止对同一张表进行不同类型的 DML 变更 (UPDATE,INSERT,DELETE)。 &#x1f514; 重大变更 工作空间设置中的「数据访问…

【Linux操作系统】命令的运行原理

文章目录 shell命令以及运行原理Linux系列学习目录 shell命令以及运行原理 Linux严格意义上说的是一个操作系统&#xff0c;我们称之为“核心&#xff08;kernel&#xff09;“ &#xff0c;但我们一般用户&#xff0c;不能直接使用kernel。而是通过kernel的“外壳”程序&…

【网站项目】294火车票订票系统

&#x1f64a;作者简介&#xff1a;拥有多年开发工作经验&#xff0c;分享技术代码帮助学生学习&#xff0c;独立完成自己的项目或者毕业设计。 代码可以私聊博主获取。&#x1f339;赠送计算机毕业设计600个选题excel文件&#xff0c;帮助大学选题。赠送开题报告模板&#xff…

Data Interpreter: An LLM Agent For Data Science 论文解读

论文地址&#xff1a;https://arxiv.org/abs/2402.18679 Github&#xff1a;MetaGPT: The Multi-Agent Framework 数据解释器&#xff08;Data Interpreter&#xff09;是一个基于大型语言模型&#xff08;LLM&#xff09;的代理&#xff0c;专门为解决数据科学问题而设计。它…

主干网络篇 | YOLOv8更换主干网络之MobileNetV3

前言:Hello大家好,我是小哥谈。MobileNetV3是一种轻量级的卷积神经网络架构,用于图像分类和目标检测任务。它是MobileNet系列的第三个版本,旨在在保持高准确性的同时减少模型的计算量和参数数量。MobileNetV3引入了一些新的设计思想和技术,以提高模型的性能。其中一项重要…

抖音用户主页如何打开词令抖音小程序?

抖音用户主页如何打开词令抖音小程序&#xff1f; 1、打开抖音主页&#xff0c;点击右上角的「搜索」&#xff1b; 2、使用抖音搜索找到小程序名称&#xff1a;词令的用户&#xff0c;并点击头像名称进入&#xff1b; 3、进入词令抖音用户主页&#xff0c;并到抖音小程序的图标…

Tensorflow 2.0 常见函数用法(一)

文章目录 0. 基础用法1. tf.cast2. tf.keras.layers.Dense3. tf.variable_scope4. tf.squeeze5. tf.math.multiply 0. 基础用法 Tensorflow 的用法不定期更新遇到的一些用法&#xff0c;之前已经包含了基础用法参考这里 &#xff0c;具体包含如下图的方法&#xff1a; 本文介…

Springboot解决跨域问题方案总结(包括Nginx,Gateway网关等)

&#x1f3f7;️个人主页&#xff1a;牵着猫散步的鼠鼠 &#x1f3f7;️系列专栏&#xff1a;Java全栈-专栏 &#x1f3f7;️个人学习笔记&#xff0c;若有缺误&#xff0c;欢迎评论区指正 目录 前言 解决跨域问题方案 1.Spring Boot 中解决跨域 1.1 通过注解跨域 1.2 通…

JavaScript高级(十)----JavaScript中的类【重述原型链】!

类 在JavaScript其实本来没有类的概念&#xff0c;哪怕是ES5以后的class&#xff0c;严格意义上来说也只是构造函数的语法糖&#xff0c;之所以喜欢称之为类&#xff0c;因为JavaScript也可以面向对象开发。 类的声明 class Person {}function Person1() {}// 上面两种写法本…

简单了解JMM

什么是JMM 对于不同的硬件和操作系统&#xff0c;有着自己的底层内存模型&#xff0c;可能导致Java程序在一些的平台可以正确并发&#xff0c;而在另一些平台出现并发错误&#xff0c;JMM是Java内存模型&#xff0c;是语言级别的内存模型&#xff0c;用于屏蔽掉各种硬件和操作…

Activiti7学习大纲及环境-Activiti7从入门到专家(2)

学习大纲 入门系列 开发环境及源码编译流程设计器核心API简单流程示例启动与结束事件边界事件中间事件用户任务手动任务接受任务服务任务脚本任务业务规则任务排他网关并行网关包容网关事件网关子流程调用活动泳池泳道执行监听器任务监听器全局监听器真实业务流程 进阶系列 …

蓝桥杯真题讲解:网络稳定性(Kruskal重构树+LCA)

蓝桥杯真题讲解&#xff1a;网络稳定性&#xff08;Kruskal重构树LCA&#xff09; 一、视频讲解二、正解代码 一、视频讲解 蓝桥杯真题讲解&#xff1a;网络稳定性&#xff08;Kruskal重构树LCA&#xff09; 二、正解代码 //kruskal重构树 lca #include<bits/stdc.h>…

lora-scripts 训练IP形象

CodeWithGPU | 能复现才是好算法CodeWithGPU | GitHub AI算法复现社区&#xff0c;能复现才是好算法https://www.codewithgpu.com/i/Akegarasu/lora-scripts/lora-trainstable-diffusion打造自己的lora模型&#xff08;使用lora-scripts&#xff09;-CSDN博客文章浏览阅读1.1k次…

Springboot+vue的高校实习管理系统(有报告)。Javaee项目,springboot vue前后端分离项目。

演示视频&#xff1a; Springbootvue的高校实习管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot vue前后端分离项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09…