加油站问题

一、题目描述

在一条环路上有 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 环形子数组的最大和

相关推荐
GG.WinGel1 小时前
质数数目(蓝桥杯练习题)
java·数据结构·算法·青少年编程·蓝桥杯
艾iYYY1 小时前
进程的状态
算法
秋天的怀念_David1 小时前
算法一千零一夜 · 第十七夜 长街点灯
java·程序人生·算法
CV爱数码2 小时前
论文精读|RED-CNN:残差编解码网络,低剂量 CT 去噪的经典基线
网络·人工智能·算法·cnn·ct·重建·x-ray
JustNow_Man2 小时前
【Harness】本地文本向量化搜索解决方案--qmd
git·python·算法
敲代码的苦132 小时前
头歌 数据结构—消防资源调度
数据结构·算法
ysu_03142 小时前
【C++ 进阶之路】(二)从内联函数到类与对象:深挖内存对齐、this 指针与三大默认成员函数
开发语言·数据结构·c++·笔记·面向对象·this
布莱克6052 小时前
C++ 中 set 与 map 的区别详解
开发语言·c++·算法·哈希
WJ.Polar2 小时前
异常、泛型、集合、Stream流
java·数据结构