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

题目
测试链接

在一条环路上有 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;
    }
相关推荐
wabs6667 小时前
关于图论【卡码网117.软件构建的思考】
数据结构·算法·软件构建·图论·卡码网
mifengxing8 小时前
LeetCode 41.缺失的第一个正数|Hard题O(n)+O(1)最优解法深度解析
java·算法·leetcode·排序算法
Tongzhi20268 小时前
从部署到运维:通芝科技无感考勤一体机的全流程效率解析
运维·数据结构·科技·算法·贪心算法
Wang's Blog8 小时前
AI Agent白手起家30: 动态示例选择器之根据长度选择 Few Shot 示例
算法
markinmarkin10 小时前
Spring 中Bean 的作用域有哪些?
java·后端·spring
oyguyteggytrrwwwrt10 小时前
自制交叉线路识别算法
算法
手写码匠10 小时前
华为云Flexus+DeepSeek征文|Dify 多智能体协同编排实战:R1 规划 + V3 执行,构建企业 Agent 团队
人工智能·深度学习·算法·aigc
山荷枝11 小时前
Java学习第十天
java·学习
晓天衡宇•评测社区11 小时前
FSR-Bench 前沿科学推理榜单发布:GPT-5.5 居首,“会推理”未必“能答对”
算法
matlabgoodboy11 小时前
计算机毕设代做|Java Python Matlab APP 全套开发设计
java·python·课程设计