当前位置: 首页 > news >正文

134. 加油站

目录

题目链接:

题目:

解题思路:

代码:

总结:


题目链接:

134. 加油站 - 力扣(LeetCode)

题目:

解题思路:

使用前缀和,若前缀和小于0,说明前面都不可以作为起始点,(可以举一个例子尝试一下(如有区间1和区间2,两段和小于0,而区间2的和大于0,那么区间1的和肯定<0,那么肯定还得重新找前缀和))最后如果加油量小于消耗量,肯定没办法走一圈,

代码:

class Solution {public int canCompleteCircuit(int[] gas, int[] cost) {int sum=0;int cur=0;int idx=0;for(int i=0;i<gas.length;i++){sum+=gas[i]-cost[i];cur+=gas[i]-cost[i];if(cur<0){idx=i+1;cur=0;}}if(sum<0){return -1;}return idx;}
}

加油站问题的一次优雅征服:从暴力到贪心的算法之旅
在算法的世界里,有些问题初看之下似乎只能用蛮力解决,但深入思考后,往往能发现其内在的规律,从而找到令人拍案叫绝的高效解法。今天我们要探讨的 “加油站问题”(Gas Station Problem)就是这样一个典型的例子。
我将带你从最直观的暴力解法入手,分析其缺陷,然后一步步引导你发现问题的核心规律,最终理解并掌握那个简洁而高效的贪心算法。
一、问题重述:我们要解决什么?
首先,让我们清晰地理解问题:
场景:有一个环形的路线,上面有 N 个加油站。
资源:
gas[i]:第 i 个加油站可以加的油量。
cost[i]:从第 i 个加油站开到下一个加油站(第 (i+1)%N 个)需要消耗的油量。
目标:你有一辆油箱容量无限的车,从其中一个加油站出发,尝试绕这个环形路线一周。你需要判断是否存在这样一个起点,使得你可以成功完成绕行。如果存在,返回这个起点的索引;如果不存在,返回 -1。
约束:
车在任何时候的油量都不能为负数。
只能按顺序从一个加油站开到下一个,不能跳着开。
这个问题的关键在于找到一个 “幸运” 的起点,从这个点开始,你的油量 “收支” 在整个环路上始终保持非负。
二、直觉与暴力:最直接的想法
面对这个问题,最直接的想法是什么?
暴力解法思路:
遍历每一个加油站 i,将其视为候选起点。
从起点 i 出发,模拟绕环一周的过程。
在模拟过程中,不断累加剩余油量 tank。tank += gas[j] - cost[j]。
如果在任何时候 tank 变为负数,说明从起点 i 出发无法完成绕行,立即停止模拟,并尝试下一个起点 i+1。
如果成功绕行一周(回到起点)且 tank 始终非负,说明找到了答案,返回 i。
如果遍历完所有起点都失败,返回 -1。
暴力解法代码(示意):
java
运行
public int canCompleteCircuitBruteForce(int[] gas, int[] cost) {
    int n = gas.length;
    for (int i = 0; i < n; i++) {
        int tank = 0;
        boolean canComplete = true;
        for (int j = 0; j < n; j++) {
            int currentStation = (i + j) % n;
            tank += gas[currentStation] - cost[currentStation];
            if (tank < 0) {
                canComplete = false;
                break;
            }
        }
        if (canComplete) {
            return i;
        }
    }
    return -1;
}
暴力解法分析:
时间复杂度:O(N^2)。因为我们有 N 个起点,每个起点最多需要遍历 N 个加油站。在 N 很大时(例如 N=10^5),这种解法会超时。
空间复杂度:O(1)。只使用了几个额外变量。
暴力解法虽然能解决问题,但效率低下。我们需要一种更聪明的方法。
三、洞察与飞跃:贪心算法的核心思想
让我们跳出模拟的思维,转而分析问题的本质。
核心观察 1:整体可行性判断
一个最基本的事实是:如果所有加油站的总油量 total_gas 小于所有路段的总消耗 total_cost,那么无论从哪里出发,都不可能绕环一周。
用数学语言表达就是:sum(gas[0...n-1]) < sum(cost[0...n-1]) => 必然无解,返回 -1。
这是一个全局的判断标准。如果这个条件不满足,我们就可以直接宣布失败。
核心观察 2:局部失败的启示(贪心的关键)
现在,假设总油量是足够的 (sum(gas) >= sum(cost)),那么必然存在至少一个起点可以成功绕行。问题是如何快速找到它。
我们来思考一下失败的情况。
假设我们从起点 start 出发,一路开到了第 i 个加油站。在到达第 i 个加油站后,我们的剩余油量为 tank。接下来,我们要从第 i 个加油站开到第 i+1 个。
tank += gas[i] (在第 i 站加油)
tank -= cost[i] (从第 i 站开到第 i+1 站)
如果在这个过程之后,tank 变成了负数,意味着什么?
意味着:从 start 到 i 之间的任何一个加油站 k,都不可能作为成功的起点。
为什么?
我们从 start 出发,到达 k 时,我们的油量 tank(start -> k) 一定是大于等于 0 的。否则我们在 k 之前就已经失败了。
我们从 k 出发,最终在 i -> i+1 这一段失败了。这说明 tank(k -> i+1) < 0。
现在,假设我们神奇地从 start 直接 “传送” 到 k,并且此时的油量就是 tank(start -> k)。那么我们从 k 出发的最终油量将是 tank(start -> k) + tank(k -> i+1)。
因为 tank(start -> k) >= 0 且 tank(k -> i+1) < 0,所以 tank(start -> k) + tank(k -> i+1) 必然小于 tank(k -> i+1)。
这意味着,如果从 k 出发都无法到达 i+1,那么从 start 出发(到达 k 时还有剩余油量)也同样无法到达 i+1。我们已经证明了这一点。
结论:一旦我们在 i -> i+1 段失败,所有位于 start 和 i 之间(包括 start,不包括 i+1)的加油站都可以被排除掉,不再作为候选起点。
这个发现是决定性的!它告诉我们,我们不需要回溯去检查那些已经被证明不可能的起点。
四、算法设计:优雅的贪心实现
基于以上两个核心观察,我们可以设计出贪心算法的步骤:
初始化变量:
totalTank:用于累加整个环路的总油量与总消耗的差值。用于最终判断是否存在解。
currentTank:用于累加从当前候选起点出发的剩余油量。
startIndex:记录当前的候选起点,初始化为 0。
遍历加油站:从第一个加油站(索引 0)开始遍历整个环路。
更新油量:在每一个加油站 i,更新 totalTank 和 currentTank。
plaintext
totalTank += gas[i] - cost[i];
currentTank += gas[i] - cost[i];
判断并更新起点:如果在到达加油站 i 后,currentTank 变为负数,这意味着从 startIndex 到 i 的这段路是 “亏本” 的,我们无法从 startIndex 出发到达 i+1。根据我们的核心观察 2,我们需要:
将候选起点更新为 i+1。因为 startIndex 到 i 之间的任何点都不可能是解。
重置 currentTank 为 0。因为我们将从新的起点 i+1 重新开始计算剩余油量。
最终判断:遍历完所有加油站后,我们检查 totalTank:
如果 totalTank < 0:说明总油量不足,无法绕行一周,返回 -1。
如果 totalTank >= 0:说明总油量足够,根据问题的数学性质,必然存在一个解。而我们在遍历过程中通过不断更新 startIndex,最终得到的 startIndex 就是那个唯一的(或其中一个)可行起点。返回 startIndex。
为什么最终的 startIndex 一定是解?因为我们已经排除了所有不可能的起点。当遍历结束时,如果总油量足够,那么剩下的这个 startIndex 必然是那个能让你 “一路绿灯” 的幸运起点。
五、代码解析:一行一行读懂它
现在,让我们来解析你提供的这段代码,它正是上述贪心算法的完美实现。
java
运行
class Solution {
    public int canCompleteCircuit(int[] gas, int[] cost) {
        // 1. 初始化变量
        int totalTank = 0; // 用于累加整个环路的总盈亏
        int currentTank = 0; // 用于累加从当前起点开始的盈亏
        int startIndex = 0; // 记录当前的候选起点

        // 2. 遍历每一个加油站
        for (int i = 0; i < gas.length; i++) {
            // 3. 更新总盈亏和当前盈亏
            totalTank += gas[i] - cost[i];
            currentTank += gas[i] - cost[i];

            // 4. 如果从当前起点出发,到不了下一站 (i -> i+1)
            if (currentTank < 0) {
                // 说明从 startIndex 到 i 的任何一个点都不能作为起点
                // 将下一个点 i+1 设为新的候选起点
                startIndex = i + 1;
                // 重置当前盈亏,因为要从新起点开始计算
                currentTank = 0;
            }
        }

        // 5. 遍历结束后,根据总盈亏判断是否存在解
        // 如果总油量 < 总消耗,返回 -1
        if (totalTank < 0) {
            return -1;
        }
        
        // 如果总油量 >= 总消耗,那么我们找到的 startIndex 就是解
        return startIndex;
    }
}
代码执行流程模拟:让我们用一个例子来走一遍流程:gas = [1, 2, 3, 4, 5], cost = [3, 4, 5, 1, 2]
i = 0:
totalTank += 1-3 → totalTank = -2
currentTank += 1-3 → currentTank = -2
currentTank < 0 为 true。
startIndex 更新为 0+1=1。
currentTank 重置为 0。
i = 1:
totalTank += 2-4 → totalTank = -4
currentTank += 2-4 → currentTank = -2
currentTank < 0 为 true。
startIndex 更新为 1+1=2。
currentTank 重置为 0。
i = 2:
totalTank += 3-5 → totalTank = -6
currentTank += 3-5 → currentTank = -2
currentTank < 0 为 true。
startIndex 更新为 2+1=3。
currentTank 重置为 0。
i = 3:
totalTank += 4-1 → totalTank = -3
currentTank += 4-1 → currentTank = 3
currentTank < 0 为 false。什么也不做。
i = 4:
totalTank += 5-2 → totalTank = 0
currentTank += 5-2 → currentTank = 6
currentTank < 0 为 false。什么也不做。
遍历结束:
totalTank 的值为 0,满足 >= 0 的条件。
最终的 startIndex 的值为 3。
函数返回 3。这与我们手动计算的结果一致(从索引 3 的加油站出发,油量变化为:4→3→6→5→0,始终非负)。
六、复杂度分析与总结
复杂度分析
时间复杂度:O(N)。我们只需要对加油站数组进行一次完整的遍历。
空间复杂度:O(1)。我们只使用了常数个额外变量来存储状态,与输入数组的大小无关。
总结
这个贪心算法之所以优雅,在于它:
高效:O(N) 的时间复杂度,完美适用于大规模数据。
简洁:代码实现非常简短,逻辑清晰。
深刻:它并非凭空猜测,而是建立在对问题本质(局部失败与全局可行性)的深刻洞察之上。
这个问题告诉我们,面对看似复杂的循环或最优解问题时,不要急于用暴力去破解。尝试去分析失败的模式,寻找可以 “剪枝” 或 “跳跃” 的机会,往往能让你找到通往高效算法的捷径。这种从失败中学习并调整策略的思想,正是贪心算法的精髓所在。
希望这篇文章能帮助你彻底理解这个问题和解法!
 

总结:
 

本文介绍了如何高效解决LeetCode 134题的加油站问题。通过分析问题本质,提出了贪心算法的优化解法:1) 使用前缀和判断可行性;2) 当当前油量为负时,直接跳过不可能作为起点的区间。该算法只需一次遍历,时间复杂度O(N),空间复杂度O(1)。相比暴力解法,该方法通过数学洞察大幅提升了效率,是贪心算法应用的经典案例。

http://www.dtcms.com/a/412705.html

相关文章:

  • php做网站如何织梦搭建商城网站
  • 信息系统项目的交付绩效域
  • 合肥建立网站做设计挣钱的网站
  • 满天星建设网站家庭网络设计方案
  • 怎么提高网站浏览量wordpress+5.0
  • 【Ubuntu 24.04.3 LTS(Noble Numbat‌)】版本安装配置 MySQL8.4.6
  • 北京西站好的平面设计灵感网站
  • 心悦免做卡领取网站中企动力企业邮箱登陆首页
  • 商密保卫战:从实践案例看企业保密之道
  • RDSInstance 全面优化:打造高效智能的数据库实例管理系统
  • 东莞市官网网站建设品牌微网站如何建立
  • 桐乡网站设计公司网站后台账户密码
  • 网站推广外链怎么做高端个人网站
  • 阳东网站seo深圳定制型网站建设
  • 女装网站建设规划书怎么写湛江网站建设优化建站
  • 网站开发相关期刊比 wordpress
  • 校园二手书交易网站开发站群管理系统cms
  • 巴西支付行业的产品格局
  • 手机网站开发费用企业网站加快企业信息化建设
  • 哪个网站域名解析建设银行积分网站
  • 天津市哪里有做网站广告的新增备案 网站名字
  • 电子商务网站策划书3000字做的最好的网站公司
  • 建设眼镜网站风格织梦dedecms教育培训网站模板
  • 个人备案网站放什么手续东莞公司注册服务平台
  • 浙江省和住房建设厅网站亚马逊电商现在好做吗
  • 【读书笔记】《寻找健康》
  • 基础设施建设的网站wordpress 图片质量
  • 北京房产东莞市seo网络推广品牌
  • AI代码时间复杂度分析工具
  • 自己建站好还是第三方建站好