滑动窗口练习(三)— 加油站问题

题目
测试链接

在一条环路上有 n 个加油站,其中第 i 个加油站有汽油 gasi 升。

你有一辆油箱容量无限的的汽车,从第 i 个加油站开往第 i+1 个加油站需要消耗汽油 costi 升。你从其中的一个加油站出发,开始时油箱为空。

给定两个整数数组 gas 和 cost ,如果你可以按顺序绕环路行驶一周,则返回出发时加油站的编号,否则返回 -1 。如果存在解,则 保证 它是 唯一 的。

解释一下这道题,如下图所示:

路程数组gas和油耗数组cost,假设从A点出发,走到B点路程为1,消耗油量为2,1 - 2 = -1,油量不够支撑走到B点,所以如果从A点出发,无法完整的绕完一圈,B、C、D同理,这道题就是看从哪个出发点出发后,可以顺利的绕完一圈(gas和cost等长)。

滑动窗口

首先,先将给定的 gas\[\] 和 cost\[\] 稍稍加工一下,一次遍历,用gasi - costi,得到的新数组中包含正负数,就是从每个点出发的路程和油耗的差值,看是否有能力到达下一个目的地。

此时如果用暴力方法解这道题的话,只需要从0 ~ N - 1每个位置循环遍历,遍历N个位置。看过程中是否出现负数,如果是负数,则说明不能完成循环,如果不是,结果 >= 0,则证明可以完成循环。

我们这里直接说用滑动窗口的解题思路:

依然是先加工gas\[\] 和 cost\[\],不同的是,我们要根据加工出来的gas\[\] - cost\[\],搞出来一个2倍gas\[\]长度的前缀和数组。

因为我们要看从 0 ~ N - 1位置中每个的出发点能否成功绕回来, gas\[\] - cost\[\] 加工出来的数组就是从每个点出发能否成功到达下一目的地的数组,所以当累加到N - 1位置时,下一步重新在加上 0 位置的值,来构造出这个2倍长度累加和数组。

这样的做的目的是,我下图中 double.length中下标4的位置,可以看做是从B点出发,又重新绕回A的0位置,下标5的位置,可以看做是从C点出发,重新绕回B点的1位置。一个数组全部可以搞定。

所有原始数组中出发的位置,在double.length中都可以将原始数组的累加和数组加工出来。

怎么加工?

假设我从D点出发,那么在gas - cost中求出来的累加和数组就是{3,2,2,4}(因为要重新往A点加绕回来),对应在double.length中就是划线部分,怎么得出来的呢?

划线数组中的每一个数,都减去划线部分的前一个数(1)。

所以,综上所述,此时我们维护一个窗口最小值,窗口范围就是gos.length,每次窗口变化后,根据窗口内最小值 - 前一个值,如果此时已然 < 0,则说明该位置不是最佳出发点。否则就认为是最佳出发点。

代码

java 复制代码
   public static int canCompleteCircuit(int[] gas, int[] cost) {
        boolean[] booleans = goodArray(gas, cost);
        for (int i = 0; i < booleans.length; i++) {
            if (booleans[i]) {
                return i;
            }
        }
        return -1;
    }

    public static boolean[] goodArray(int[] gas, int[] cost) {
        int N = gas.length;
        int M = N << 1;

        int[] arr = new int[M];

        for (int i = 0; i < N; i++) {
            arr[i] = gas[i] - cost[i];
            arr[N + i] = gas[i] - cost[i];
        }

        for (int j = 1; j < M; j++) {
            arr[j] += arr[j - 1];
        }

        LinkedList<Integer> w = new LinkedList<>();

        for (int i = 0; i < N; i++) {
            while (!w.isEmpty() && arr[w.peekLast()] >= arr[i]) {
                w.pollLast();
            }
            w.addLast(i);
        }

        boolean[] ans = new boolean[N];
        for (int offset = 0, i = 0, j = N; j < M; offset = arr[i++], j++) {
            if (arr[w.peekFirst()] - offset >= 0) {
                ans[i] = true;
            }
            if (w.peekFirst() == i) {
                w.pollFirst();
            }
            while (!w.isEmpty() && arr[w.peekLast()] >= arr[j]) {
                w.pollLast();
            }
            w.addLast(j);
        }
        return ans;
    }
相关推荐
非优秀程序员1 天前
第05章 播放器大战,暴风、快播与那一夜|《互联网产品恩仇录》
算法
非优秀程序员1 天前
第04章 邮箱大战,163 与 QQ 的暗战|《互联网产品恩仇录》
java·vue.js·架构
知已难寻1 天前
Docker 入门:容器技术与 Docker 基础
java·运维·服务器·docker·云原生·容器
文丑颜不良啊1 天前
LeetCode2481~2500题目解析
java·leetcode
Flynn Liang1 天前
同一需求三套写法:用 Spring AI、LangChain4j、AgentScope Java 各实现一个带工具调用的 RAG 助手(实战对比)
java·人工智能·大模型·架构设计·spring ai·langchain4j·agentscope
聪明的笨猪猪1 天前
实际工程问题:AI 生成 Spring Cache(@Cacheable / @CachePut / @CacheEvict)缓存注解踩坑
java·经验分享·面试·ai编程
愿有所息1 天前
记一次 ARM64 + JDK8 上 fastjson2 的诡异数据错乱
java
秋饼1 天前
MCP 2026-07-28 无状态协议大改版:Spring AI 2.1 多轮往返请求与网关路由落地
java·ai·技术分享·后端开发
SL_staff1 天前
JVS-BI 实战:业务人员如何用零代码 ELT + 可视化配置构建实时经营分析闭环
java·spring·全栈