加油站问题

一、题目描述

在一条环路上有 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 号加油站出发,可以走完一圈。

示例 2:

输入: gas = 2,3,4, cost = 3,4,3

输出: -1 解释:无论从哪个加油站出发,都无法走完一圈。

二、思路分析

核心前置结论:所有加油站总油量 ≥ 总消耗,才一定存在解;否则直接返回 - 1。

  1. 暴力思路:依次选取每个加油站作为起点,模拟绕环行驶,中途油量小于 0 说明起点不行,换下一个起点。
  2. 贪心跳跃优化:如果从起点 start 走到 i 位置时油量不足,说明[start, i]区间内所有加油站都不能作为合法起点,起点直接跳到i+1,跳过无效候选。
  3. 前缀和最小值优化:把环形展开,计算差分前缀和,前缀和最小位置的下一站就是合法起点。

三、基础实现

cpp 复制代码
class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        int n = gas.size();
        // 依次尝试每一个加油站作为起点
        for (int start = 0; start < n; ++start) {
            int curOil = 0;  // 当前油箱油量
            bool ok = true;
            // 从start出发,走n步绕一圈
            for (int step = 0; step < n; ++step) {
                int pos = (start + step) % n;
                curOil += gas[pos] - cost[pos];
                // 油量不够,无法到达下一站
                if (curOil < 0) {
                    ok = false;
                    break;
                }
            }
            if (ok) {
                return start;
            }
        }
        // 所有起点都失败
        return -1;
    }
};

时间复杂度:O(n^2),n 是加油站数量,每个起点最多遍历 n 个站点,数据量大的时候超时,仅用于理解题目逻辑。

空间复杂度:O(1),只用常数临时变量。

四、优化一:跳跃贪心

优化点 :暴力里很多起点是无效的。走到 i 没油了,start~i 之间任意点出发,走到 i 依然缺油,直接把起点设置为i+1,省去大量无效遍历。

cpp 复制代码
class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        int n = gas.size();
        int total = 0;    // 全局总净油量,判断是否有解
        int currOil = 0;  // 当前起点累计净油量
        int start = 0;    // 候选起点

        for (int i = 0; i < n; ++i) {
            int diff = gas[i] - cost[i];
            total += diff;
            currOil += diff;

            // 当前油量不足,[start, i]全部不能作为起点
            if (currOil < 0) {
                start = i + 1;
                currOil = 0; // 重置油量,从新起点重新统计
            }
        }
        // 总油量>=0才有解,返回记录的起点;否则无解
        return total >= 0 ? start : -1;
    }
};

时间复杂度:O(n),仅遍历数组一次。

空间复杂度:O(1),常数空间。

五、优化二:前缀和找最小值

核心思路:把环拆成线性,计算每个位置差分的前缀和。前缀和最小的位置,说明在这里最容易缺油,它下一个位置就是能保证全程油量不亏的起点。

cpp 复制代码
class Solution {
public:
    int canCompleteCircuit(vector<int>& gas, vector<int>& cost) {
        int n = gas.size();
        int total = 0;
        int preSum = 0;
        int minPreSum = INT_MAX;
        int minIdx = 0;

        for (int i = 0; i < n; ++i) {
            int diff = gas[i] - cost[i];
            total += diff;
            preSum += diff;
            // 更新最小前缀和以及对应下标
            if (preSum < minPreSum) {
                minPreSum = preSum;
                minIdx = i;
            }
        }
        if (total < 0) return -1;
        // 最小前缀位置的下一个点就是起点,取模防止越界
        return (minIdx + 1) % n;
    }
};

时间复杂度:O(n),一次遍历求前缀和。

空间复杂度:O(1)。

六、总结

加油站是贪心经典题,两种 O (n) 思路侧重点不一样:

  1. 跳跃贪心:逻辑直观,代码简短,面试优先写这个;核心是失败区间全部舍弃。
  2. 前缀和最小值:偏数学推导,适合被追问还有没有别的 O (n) 解法时讲。

同类推荐题目: LeetCode 53 最大子数组和、LeetCode 918 环形子数组的最大和

相关推荐
文丑颜不良啊14 分钟前
LeetCode2921~2940题目解析
java·leetcode
Pniubi32 分钟前
动态规划入门 ( - 力扣509斐波那契数)
算法·动态规划
吴长建先生重名了2 小时前
GitHub Actions部署报表系统踩坑记:幂等性与限
算法
贝壳6902 小时前
一个2026年2月才上传的库,凭什么敢碰2009年就有人写烂的题目
算法
文丑颜不良啊2 小时前
LeetCode2901~2920题目解析
java·leetcode
贝壳6903 小时前
同一道三亿位选型题,三个AI给了我三个答案,我把它们拉进同一台机器复跑
算法
文丑颜不良啊5 小时前
LeetCode2861~2880题目解析
java·leetcode
岑梓铭5 小时前
《考研408数据结构》第八章(排序)复习笔记
数据结构·笔记
青少儿编程课堂5 小时前
蓝桥杯青少组图形化 + 全国青少年信息素养大赛 + 电子学会图形化考级:校园大富翁经营闯关解析
c++·python·算法·bfs·信息学竞赛
在所不辞兄5 小时前
为什么在新能源汽车电驱与热管理开发中,AI驱动的数字孪生和数据驱动仿真正在部分替代对物理台架与实车标定的依赖?
人工智能·深度学习·神经网络·算法·机器学习