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

题目
测试链接

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

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

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

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

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

滑动窗口

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

此时如果用暴力方法解这道题的话,只需要从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;
    }
相关推荐
吃饱很舒服3 分钟前
kotlin distinctBy 使用
android·java·开发语言·前端·kotlin
老马啸西风6 分钟前
MySQL-18-mysql source 执行 sql 文件时中文乱码
java
源码宝11 分钟前
基于java语言+ Vue+ElementUI+ MySQL8.0.36数字化产科管理平台源码,妇幼信息化整体解决方案
java·源代码·产科电子病历系统源码·医院产科信息管理系统源码·数字化产科管理平台源码
FREE技术42 分钟前
基于java+springboot+vue实现的畅销图书推荐系统(文末源码+lw+ppt)23-500
java·vue.js·spring boot
ToBeWhatYouWannaBe.44 分钟前
代码随想录-Day49
java·数据结构·算法·leetcode
Little Tian1 小时前
插入排序——C语言
c语言·数据结构·算法·排序算法
creative_mind1 小时前
My Greedy Algorithm(贪心算法)之路(一)
c++·算法·贪心算法
彧A1 小时前
数据库的学习(4)
java·开发语言·数据库
Jinyi5031 小时前
Spring Boot 高级配置:如何轻松定义和读取自定义配置
java·spring boot·后端·spring·java-ee·maven·intellij-idea
阳光男孩011 小时前
力扣1546.和为目标值且不重叠的非空子数组的最大数目
数据结构·算法·leetcode